mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2026-01-02 22:59:14 +03:00
Compare commits
46 Commits
Muse.16.15
...
Muse.16.15
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c7ccdf28 | ||
|
|
0002256630 | ||
|
|
b3ee25e3be | ||
|
|
d53ae66547 | ||
|
|
26708ea51a | ||
|
|
9c711d73f5 | ||
|
|
d0ac871a3b | ||
|
|
38fee20680 | ||
|
|
12ae3d08b6 | ||
|
|
2931a5100e | ||
|
|
c148cd16ae | ||
|
|
38fb90d179 | ||
|
|
61ff6a8915 | ||
|
|
add6ff37ba | ||
|
|
ae2ad85dec | ||
|
|
a72f471c35 | ||
|
|
f1108332d9 | ||
|
|
d10d12a85b | ||
|
|
956392ebfd | ||
|
|
f4ddc450a1 | ||
|
|
926c567345 | ||
|
|
0b077b5234 | ||
|
|
45cae64c83 | ||
|
|
7c1a1081c4 | ||
|
|
4227fc9603 | ||
|
|
c7e4d9711c | ||
|
|
e09837158c | ||
|
|
067a1f2800 | ||
|
|
ad4d5db2f1 | ||
|
|
55053d5941 | ||
|
|
e9ccd8eef7 | ||
|
|
aa8554b722 | ||
|
|
fd502a01d7 | ||
|
|
948a02efee | ||
|
|
d1858c3cc3 | ||
|
|
eaa35b3677 | ||
|
|
9f170020e2 | ||
|
|
809b55579f | ||
|
|
85a3bf8836 | ||
|
|
3df589d7ab | ||
|
|
118462f5d7 | ||
|
|
ca7670f754 | ||
|
|
b154f60e8e | ||
|
|
e2b13a8a3f | ||
|
|
da87c859d0 | ||
|
|
3a424d0d58 |
90
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
90
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: How to Submit an Issue for the squeezelite-esp32 Project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
To help us resolve your issue as quickly as possible, please follow these guidelines when submitting an issue. Providing all the necessary information will save both your time and ours.
|
||||
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### Preliminary Information
|
||||
|
||||
1. **Firmware Version**: Specify the version of the firmware you are using.
|
||||
2. **Plugin Version**: Mention the version of the plugin installed on your LMS (Logitech Media Server).
|
||||
|
||||
### Hardware Details
|
||||
|
||||
Please describe your hardware setup:
|
||||
|
||||
- **ESP32 Module**: For example, ESP32 WROVER, ESP32-S3, etc.
|
||||
- **Board Type**: If applicable, e.g., ESP32 audio kit, etc.
|
||||
- **DAC Chip**: Specify the DAC chip you are using.
|
||||
- **Additional Hardware**: Include details about any other hardware like rotary controls, buttons, screens (SPI, I2C), Ethernet, IO expansion, etc.
|
||||
|
||||
### NVS Settings
|
||||
|
||||
Follow these steps to share your NVS settings:
|
||||
|
||||
1. Open the web UI of your device.
|
||||
2. Click on the "Credit" tab.
|
||||
3. Enable the "Show NVS Editor" checkbox. This allows you to view or change the NVS configuration even when not in recovery mode.
|
||||
4. Navigate to the "NVS Editor" tab.
|
||||
5. Scroll to the bottom and click "Download Config".
|
||||
6. Share the downloaded content here.
|
||||
|
||||
<details>
|
||||
<pre><code>
|
||||
Your log content here
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
|
||||
### Logs
|
||||
|
||||
To share logs:
|
||||
|
||||
1. Connect your player to a computer using a USB cable. Use a built-in Serial-USB adapter if your player has one, or an external USB adapter otherwise.
|
||||
2. Go to the [web installer](https://sle118.github.io/squeezelite-esp32-installer/).
|
||||
3. Click "Connect to Device".
|
||||
4. Select the appropriate serial port.
|
||||
5. Click "Logs And Console".
|
||||
6. Download the logs and share them here. Please remove any sensitive information like Wi-Fi passwords or MAC addresses.
|
||||
- **If the problem occurs soon after booting**: Share the full log until the issue occurs.
|
||||
- **If the problem occurs later during playback**: Trim the logs to include information just before and after the problem occurs.
|
||||
|
||||
#### Example Log
|
||||
|
||||
Here's an example log for reference. Make sure to obfuscate sensitive information like Wi-Fi passwords, MAC addresses, and change IP addresses to something more generic.
|
||||
|
||||
<details>
|
||||
<pre><code>
|
||||
=== START OF LOG ===
|
||||
Example of log from the console
|
||||
rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
|
||||
configsip: 0, SPIWP:0xee
|
||||
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
|
||||
mode:DIO, clock div:1
|
||||
...
|
||||
I (1041) cpu_start: Application information:
|
||||
I (1044) cpu_start: Project name: Squeezelite-ESP32
|
||||
I (1050) cpu_start: App version: I2S-4MFlash-1336
|
||||
I (1055) cpu_start: Compile time: Aug 12 2023 01:20:18
|
||||
I (1062) cpu_start: ELF file SHA256: 34241d6e99fd1d6b...
|
||||
I (1068) cpu_start: ESP-IDF: v4.3.5-dirty
|
||||
...
|
||||
I (1133) heap_init: At 40094A8C len 0000B574 (45 KiB): IRAM
|
||||
I (1139) spiram: Adding pool of 4066K of external SPI memory to heap allocator
|
||||
=== END OF LOG ===
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
### Issue Description
|
||||
|
||||
1. **Observed Behavior**: Describe what you think is wrong.
|
||||
2. **Expected Behavior**: Describe what you expect should happen.
|
||||
3. **Steps to Reproduce**: Provide a step-by-step guide on how to replicate the issue.
|
||||
51
README.md
51
README.md
@@ -2,14 +2,14 @@
|
||||
# Squeezelite-esp32
|
||||
|
||||
## 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
|
||||
Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. 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)
|
||||
- Stream direcly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)
|
||||
- Stream directly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot))
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
But squeezelite-esp32 is highly extensible and you can add
|
||||
|
||||
@@ -17,12 +17,13 @@ But squeezelite-esp32 is highly extensible and you can add
|
||||
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
|
||||
- [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
|
||||
- [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
|
||||
- [Ethernet](#ethernet-required-unpublished-version-43) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
|
||||
- [LED strip](#led-strip) for VU-meter
|
||||
- [Ethernet](#ethernet) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
|
||||
|
||||
Other features include
|
||||
|
||||
- Resampling
|
||||
- 10-bands equalizer
|
||||
- Resampling (16 bits mode)
|
||||
- 10-bands equalizer (16 bits mode)
|
||||
- Automatic initial setup using any WiFi device
|
||||
- Full web interface for further configuration/management
|
||||
- Firmware over-the-air update
|
||||
@@ -53,6 +54,8 @@ In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommend
|
||||
|
||||
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.
|
||||
|
||||
**IMPORTANT: on esp32 (not esp32-s3), using Spotify with SPDIF produces stuttering audio when "stats" are enabled. You MUST disable them**
|
||||
|
||||
## 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))
|
||||
|
||||
@@ -65,6 +68,9 @@ Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can
|
||||
|
||||
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.
|
||||
|
||||
### Raw WROOM esp32-s3 module
|
||||
The esp32-s3 based modules like [this]@(https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf) are also supported but requires esp-idf 4.4. It is not yet part of official releases, but it compiles & runs. The s3 does not have bluetooth audio. Note that CPU performances are greatly enhanced.
|
||||
|
||||
### 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).
|
||||
|
||||
@@ -119,7 +125,7 @@ or
|
||||
- dac_config: `model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,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
|
||||
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/APU (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
|
||||
|
||||
- dac_config: `model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22`
|
||||
- dac_controlset:
|
||||
@@ -130,7 +136,7 @@ This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1
|
||||
- display_config: `SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip`
|
||||
|
||||
### ESP32-WROVER + I2S DAC
|
||||
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
|
||||
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work but many others also do. PCM5012 DACs can be hooked up via:
|
||||
|
||||
I2S - WROVER
|
||||
VCC - 3.3V
|
||||
@@ -174,7 +180,7 @@ Default and only "host" is 1 as others are used already by flash and spiram. The
|
||||
### 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 named configurations
|
||||
```
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
|
||||
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|WM8978|ES8388|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
|
||||
```
|
||||
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). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
|
||||
|
||||
@@ -251,7 +257,7 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
|
||||
- 'artwork' enables coverart display, if available (does not work for Bluetooth). The optional parameter indicates if the artwork should be resized (1) to fit the available space. Note that the built-in resizer can only do 2,4 and 8 downsizing, so fit is not optimal. The artwork will be placed at the right of the display for landscape displays and underneath the two information lines for others (there is no user option to tweak that).
|
||||
|
||||
### Infrared
|
||||
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
|
||||
You can use any IR receiver compatible with NEC protocol (38KHz) or RC5. Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
|
||||
|
||||
The IR codes are send "as is" to LMS, so only a Logitech SB remote from Boom, Classic or Touch will work. I think the file Slim_Devices_Remote.ir in the "server" directory of LMS can be modified to adapt to other codes, but I've not tried that.
|
||||
|
||||
@@ -266,16 +272,18 @@ GPIO can be set to GND provide or Vcc at boot. This is convenient to power devic
|
||||
|
||||
The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
|
||||
|
||||
The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off
|
||||
|
||||
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
|
||||
|
||||
You can set the Green and Red status led as well with their respective active state (:0 or :1)
|
||||
You can set the Green and Red status led as well with their respective active state (:0 or :1) or specific the chipset if you use addressable RGB led.
|
||||
|
||||
The `<ir>` parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
|
||||
|
||||
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>]
|
||||
<gpio>=Vcc|GND|amp[:1|0]|power[:1:0]|ir[:nec|rc5]|jack[:0|1]|green[:0|1|ws2812]|red[:0|1|ws2812]|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 named configurations ((SqueezeAMP, Muse ...) where these are forced at runtime.
|
||||
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
|
||||
@@ -287,21 +295,21 @@ Each expander can support up to 32 GPIO. To use an expander for buttons, an inte
|
||||
|
||||
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
|
||||
```
|
||||
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>|100][,count=<n>|16][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
|
||||
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
|
||||
```
|
||||
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
|
||||
- addr: chip i2c/spi address (decimal)
|
||||
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
|
||||
- cs (SPI): gpio used for Chip Select
|
||||
- speed (SPI): speed of the SPI bus for that device (in Hz)
|
||||
- base: GPIO numbering offset to use everywhere else (default 40)
|
||||
- base: GPIO numbering offset to use everywhere else (default 40 on esp32 and 48 on esp32-s3)
|
||||
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
|
||||
- intr: real GPIO to use as interrupt.
|
||||
|
||||
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
|
||||
|
||||
### LED
|
||||
See [set_GPIO](#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
|
||||
See [set_GPIO](#set-gpio) for how to set the green and red LEDs (including addressable RGB ones). In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
|
||||
```
|
||||
[green=0..100][,red=0..100]
|
||||
```
|
||||
@@ -447,7 +455,7 @@ There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctr
|
||||
|
||||
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
|
||||
### Ethernet
|
||||
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
|
||||
Wired ethernet is supported by esp32 with various options but squeezeESP32 is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
|
||||
|
||||
**Note:** Touch buttons that can be find on some board like the LyraT V4.3 are not supported currently.
|
||||
|
||||
@@ -492,7 +500,7 @@ model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
|
||||
### Battery / ADC
|
||||
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>[,atten=<0|1|2|3>]
|
||||
channel=0..7,scale=<scale>,cells=<1..3>[,atten=<0|1|2|3>]
|
||||
```
|
||||
NB: Set parameter to empty to disable battery reading. For named configurations (SqueezeAMP, Muse ...), this is ignored (except for SqueezeAMP where number of cells is required)
|
||||
|
||||
@@ -542,13 +550,14 @@ For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and
|
||||
|
||||
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
|
||||
See squeezelite 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
|
||||
- s <name>|-disable: connect to a specific server. Use -disable to not search for any server
|
||||
|
||||
**There is a safety feature to protect against WiFi/LMS connection loss that forces a reboot every few minutes when there is no LMS server detected. In case you don't want to use LMS at all, please set the server name to "-disable" on squeezelite command line ("-s -disable")**
|
||||
|
||||
@@ -572,11 +581,11 @@ docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-id
|
||||
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
|
||||
|
||||
### Manual Install of ESP-IDF
|
||||
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install.
|
||||
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install. You also need a few extra Python libraries for cspot by addingsudo `pip3 install protobuf grpcio-tools`
|
||||
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 **
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
|
||||
|
||||
## Building Squeezelite-esp32
|
||||
## Building SqueezeESP32
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
|
||||
|
||||
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
|
||||
|
||||
@@ -297,6 +297,12 @@ CONFIG_AUDIO_CONTROLS=""
|
||||
CONFIG_AMP_GPIO=-1
|
||||
# end of AMP configuration
|
||||
|
||||
#
|
||||
# POWER configuration
|
||||
#
|
||||
CONFIG_POWER_GPIO=-1
|
||||
# end of POWER configuration
|
||||
|
||||
#
|
||||
# Audio JACK
|
||||
#
|
||||
|
||||
@@ -264,6 +264,12 @@ CONFIG_CSPOT_SINK=y
|
||||
#
|
||||
# end of Display Screen
|
||||
|
||||
#
|
||||
# POWER configuration
|
||||
#
|
||||
CONFIG_POWER_GPIO=-1
|
||||
# end of POWER configuration
|
||||
|
||||
#
|
||||
# Various I/O
|
||||
#
|
||||
|
||||
@@ -288,6 +288,12 @@ CONFIG_AUDIO_CONTROLS=""
|
||||
CONFIG_AMP_GPIO=-1
|
||||
# end of AMP configuration
|
||||
|
||||
#
|
||||
# POWER configuration
|
||||
#
|
||||
CONFIG_POWER_GPIO=-1
|
||||
# end of POWER configuration
|
||||
|
||||
#
|
||||
# Compiler options
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
if(IDF_TARGET STREQUAL esp32)
|
||||
if(IDF_TARGET STREQUAL esp32 AND IDF_VERSION_MAJOR EQUAL 4 AND IDF_VERSION_MINOR LESS 4)
|
||||
set(lib_dir ${build_dir}/esp-idf)
|
||||
set(driver esp32/i2s.c esp32/i2s_hal.c)
|
||||
set(driver esp32/i2s.c)
|
||||
string(REPLACE ".c" ".c.obj" driver_obj "${driver}")
|
||||
|
||||
idf_component_register( SRCS ${driver}
|
||||
@@ -19,4 +19,6 @@ if(IDF_TARGET STREQUAL esp32)
|
||||
COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj}
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
message(STATUS "==> NO OVERRIDE <==")
|
||||
endif()
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The HAL layer for I2S (common part)
|
||||
|
||||
#include "soc/soc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/i2s_hal.h"
|
||||
|
||||
#define I2S_TX_PDM_FP_DEF 960 // Set to the recommended value(960) in TRM
|
||||
#define I2S_RX_PDM_DSR_DEF 0
|
||||
|
||||
void i2s_hal_set_tx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
|
||||
{
|
||||
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
|
||||
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
} else {
|
||||
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
|
||||
}
|
||||
i2s_ll_set_tx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
#if SOC_I2S_SUPPORTS_DMA_EQUAL
|
||||
i2s_ll_set_tx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void i2s_hal_set_rx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
|
||||
{
|
||||
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
|
||||
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
} else {
|
||||
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
|
||||
}
|
||||
i2s_ll_set_rx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
#if SOC_I2S_SUPPORTS_DMA_EQUAL
|
||||
i2s_ll_set_rx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void i2s_hal_set_in_link(i2s_hal_context_t *hal, uint32_t bytes_num, uint32_t addr)
|
||||
{
|
||||
i2s_ll_set_in_link_addr(hal->dev, addr);
|
||||
i2s_ll_set_rx_eof_num(hal->dev, bytes_num);
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_PDM
|
||||
void i2s_hal_tx_pdm_cfg(i2s_hal_context_t *hal, uint32_t fp, uint32_t fs)
|
||||
{
|
||||
i2s_ll_tx_pdm_cfg(hal->dev, fp, fs);
|
||||
}
|
||||
|
||||
void i2s_hal_get_tx_pdm(i2s_hal_context_t *hal, uint32_t *fp, uint32_t *fs)
|
||||
{
|
||||
i2s_ll_get_tx_pdm(hal->dev, fp, fs);
|
||||
}
|
||||
|
||||
void i2s_hal_rx_pdm_cfg(i2s_hal_context_t *hal, uint32_t dsr)
|
||||
{
|
||||
i2s_ll_rx_pdm_cfg(hal->dev, dsr);
|
||||
}
|
||||
|
||||
void i2s_hal_get_rx_pdm(i2s_hal_context_t *hal, uint32_t *dsr)
|
||||
{
|
||||
i2s_ll_get_rx_pdm(hal->dev, dsr);
|
||||
}
|
||||
#endif
|
||||
|
||||
void i2s_hal_set_clk_div(i2s_hal_context_t *hal, int div_num, int div_a, int div_b, int tx_bck_div, int rx_bck_div)
|
||||
{
|
||||
i2s_ll_set_clkm_div_num(hal->dev, div_num);
|
||||
i2s_ll_set_clkm_div_a(hal->dev, div_a);
|
||||
i2s_ll_set_clkm_div_b(hal->dev, div_b);
|
||||
i2s_ll_set_tx_bck_div_num(hal->dev, tx_bck_div);
|
||||
i2s_ll_set_rx_bck_div_num(hal->dev, rx_bck_div);
|
||||
}
|
||||
|
||||
void i2s_hal_set_tx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
|
||||
{
|
||||
i2s_ll_set_tx_bits_mod(hal->dev, bits);
|
||||
}
|
||||
|
||||
void i2s_hal_set_rx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
|
||||
{
|
||||
i2s_ll_set_rx_bits_mod(hal->dev, bits);
|
||||
}
|
||||
|
||||
void i2s_hal_reset(i2s_hal_context_t *hal)
|
||||
{
|
||||
// Reset I2S TX/RX module first, and then, reset DMA and FIFO.
|
||||
i2s_ll_reset_tx(hal->dev);
|
||||
i2s_ll_reset_rx(hal->dev);
|
||||
i2s_ll_reset_dma_in(hal->dev);
|
||||
i2s_ll_reset_dma_out(hal->dev);
|
||||
i2s_ll_reset_rx_fifo(hal->dev);
|
||||
i2s_ll_reset_tx_fifo(hal->dev);
|
||||
}
|
||||
|
||||
void i2s_hal_start_tx(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_start_out_link(hal->dev);
|
||||
i2s_ll_start_tx(hal->dev);
|
||||
}
|
||||
|
||||
void i2s_hal_start_rx(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_start_in_link(hal->dev);
|
||||
i2s_ll_start_rx(hal->dev);
|
||||
}
|
||||
|
||||
void i2s_hal_stop_tx(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_stop_out_link(hal->dev);
|
||||
i2s_ll_stop_tx(hal->dev);
|
||||
}
|
||||
|
||||
void i2s_hal_stop_rx(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_stop_in_link(hal->dev);
|
||||
i2s_ll_stop_rx(hal->dev);
|
||||
}
|
||||
|
||||
void i2s_hal_format_config(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
|
||||
{
|
||||
switch (i2s_config->communication_format) {
|
||||
case I2S_COMM_FORMAT_STAND_MSB:
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
i2s_ll_set_tx_format_msb_align(hal->dev);
|
||||
}
|
||||
if (i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_set_rx_format_msb_align(hal->dev);
|
||||
}
|
||||
break;
|
||||
case I2S_COMM_FORMAT_STAND_PCM_SHORT:
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
i2s_ll_set_tx_pcm_long(hal->dev);
|
||||
}
|
||||
if (i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_set_rx_pcm_long(hal->dev);
|
||||
}
|
||||
break;
|
||||
case I2S_COMM_FORMAT_STAND_PCM_LONG:
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
i2s_ll_set_tx_pcm_short(hal->dev);
|
||||
}
|
||||
if (i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_set_rx_pcm_short(hal->dev);
|
||||
}
|
||||
break;
|
||||
default: //I2S_COMM_FORMAT_STAND_I2S
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
i2s_ll_set_tx_format_philip(hal->dev);
|
||||
}
|
||||
if (i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_set_rx_format_philip(hal->dev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void i2s_hal_config_param(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
|
||||
{
|
||||
//reset i2s
|
||||
i2s_ll_reset_tx(hal->dev);
|
||||
i2s_ll_reset_rx(hal->dev);
|
||||
|
||||
//reset dma
|
||||
i2s_ll_reset_dma_in(hal->dev);
|
||||
i2s_ll_reset_dma_out(hal->dev);
|
||||
|
||||
i2s_ll_enable_dma(hal->dev);
|
||||
|
||||
i2s_ll_set_lcd_en(hal->dev, 0);
|
||||
i2s_ll_set_camera_en(hal->dev, 0);
|
||||
|
||||
i2s_ll_set_dscr_en(hal->dev, 0);
|
||||
|
||||
i2s_ll_set_tx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
|
||||
i2s_ll_set_tx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
|
||||
i2s_ll_set_tx_mono(hal->dev, 0);
|
||||
|
||||
i2s_ll_set_rx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
|
||||
i2s_ll_set_rx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
|
||||
i2s_ll_set_rx_mono(hal->dev, 0);
|
||||
|
||||
i2s_ll_set_dscr_en(hal->dev, 1); //connect dma to fifo
|
||||
|
||||
i2s_ll_stop_tx(hal->dev);
|
||||
i2s_ll_stop_rx(hal->dev);
|
||||
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
int order = i2s_config->bits_per_sample == 32 ? 0 : 1;
|
||||
i2s_ll_set_tx_msb_right(hal->dev, order);
|
||||
i2s_ll_set_tx_right_first(hal->dev, ~order);
|
||||
|
||||
i2s_ll_set_tx_slave_mod(hal->dev, 0); // Master
|
||||
i2s_ll_set_tx_fifo_mod_force_en(hal->dev, 1);
|
||||
|
||||
if (i2s_config->mode & I2S_MODE_SLAVE) {
|
||||
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
|
||||
}
|
||||
}
|
||||
|
||||
if (i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_set_rx_msb_right(hal->dev, 0);
|
||||
i2s_ll_set_rx_right_first(hal->dev, 0);
|
||||
i2s_ll_set_rx_slave_mod(hal->dev, 0); // Master
|
||||
i2s_ll_set_rx_fifo_mod_force_en(hal->dev, 1);
|
||||
|
||||
if (i2s_config->mode & I2S_MODE_SLAVE) {
|
||||
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
|
||||
}
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_PDM
|
||||
if (!(i2s_config->mode & I2S_MODE_PDM)) {
|
||||
i2s_ll_set_rx_pdm_en(hal->dev, 0);
|
||||
i2s_ll_set_tx_pdm_en(hal->dev, 0);
|
||||
} else {
|
||||
if (i2s_config->mode & I2S_MODE_TX) {
|
||||
i2s_ll_tx_pdm_cfg(hal->dev, I2S_TX_PDM_FP_DEF, i2s_config->sample_rate/100);
|
||||
}
|
||||
if(i2s_config->mode & I2S_MODE_RX) {
|
||||
i2s_ll_rx_pdm_cfg(hal->dev, I2S_RX_PDM_DSR_DEF);
|
||||
}
|
||||
// PDM mode have nothing to do with communication format configuration.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC_DAC
|
||||
if (i2s_config->mode & (I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN)) {
|
||||
if (i2s_config->mode & I2S_MODE_DAC_BUILT_IN) {
|
||||
i2s_ll_build_in_dac_ena(hal->dev);
|
||||
}
|
||||
if (i2s_config->mode & I2S_MODE_ADC_BUILT_IN) {
|
||||
i2s_ll_build_in_adc_ena(hal->dev);
|
||||
i2s_ll_set_rx_chan_mod(hal->dev, 1);
|
||||
i2s_ll_set_rx_fifo_mod(hal->dev, 1);
|
||||
i2s_ll_set_rx_mono(hal->dev, 0);
|
||||
}
|
||||
// Buildin ADC and DAC have nothing to do with communication format configuration.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
i2s_hal_format_config(hal, i2s_config);
|
||||
}
|
||||
|
||||
void i2s_hal_enable_master_mode(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_set_tx_slave_mod(hal->dev, 0); //MASTER Slave
|
||||
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
|
||||
}
|
||||
|
||||
void i2s_hal_enable_slave_mode(i2s_hal_context_t *hal)
|
||||
{
|
||||
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
|
||||
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
|
||||
}
|
||||
|
||||
void i2s_hal_init(i2s_hal_context_t *hal, int i2s_num)
|
||||
{
|
||||
//Get hardware instance.
|
||||
hal->dev = I2S_LL_GET_HW(i2s_num);
|
||||
}
|
||||
@@ -19,6 +19,12 @@
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#else
|
||||
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
|
||||
#endif
|
||||
|
||||
static struct GDS_Device Display;
|
||||
static struct GDS_BacklightPWM PWMConfig;
|
||||
|
||||
@@ -34,7 +40,7 @@ struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], s
|
||||
ledc_timer_config_t PWMTimer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
.freq_hz = 5000,
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
.speed_mode = LEDC_SPEED_MODE,
|
||||
.timer_num = PWMConfig.Timer,
|
||||
};
|
||||
ledc_timer_config(&PWMTimer);
|
||||
@@ -188,7 +194,7 @@ bool GDS_Init( struct GDS_Device* Device ) {
|
||||
.channel = Device->Backlight.Channel,
|
||||
.duty = Device->Backlight.PWM,
|
||||
.gpio_num = Device->Backlight.Pin,
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
.speed_mode = LEDC_SPEED_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = PWMConfig.Timer,
|
||||
};
|
||||
@@ -231,8 +237,8 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
|
||||
else if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
|
||||
ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
|
||||
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
|
||||
ledc_set_duty( LEDC_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
|
||||
ledc_update_duty( LEDC_SPEED_MODE, Device->Backlight.Channel );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <math.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "globdefs.h"
|
||||
#include "led_strip.h"
|
||||
#include "platform_config.h"
|
||||
#include "led_vu.h"
|
||||
@@ -28,7 +29,6 @@
|
||||
static const char *TAG = "led_vu";
|
||||
|
||||
#define LED_VU_STACK_SIZE (3*1024)
|
||||
#define LED_VU_RMT_INTR_NUM 20U
|
||||
|
||||
#define LED_VU_PEAK_HOLD 6U
|
||||
|
||||
@@ -39,12 +39,6 @@ static const char *TAG = "led_vu";
|
||||
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
struct led_strip_t* led_display = NULL;
|
||||
static EXT_RAM_ATTR struct led_strip_t led_strip_config = {
|
||||
.rgb_led_type = RGB_LED_TYPE_WS2812,
|
||||
.rmt_channel = RMT_CHANNEL_1,
|
||||
.rmt_interrupt_num = LED_VU_RMT_INTR_NUM,
|
||||
.gpio = -1,
|
||||
};
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
int gpio;
|
||||
@@ -96,24 +90,26 @@ void led_vu_init()
|
||||
strip.vu_odd = strip.length - 1;
|
||||
|
||||
// create driver configuration
|
||||
struct led_strip_t led_strip_config = { .rgb_led_type = RGB_LED_TYPE_WS2812 };
|
||||
led_strip_config.access_semaphore = xSemaphoreCreateBinary();
|
||||
led_strip_config.led_strip_length = strip.length;
|
||||
led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
|
||||
led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
|
||||
led_strip_config.gpio = strip.gpio;
|
||||
led_strip_config.rmt_channel = rmt_system_base_channel++;
|
||||
|
||||
// initialize driver
|
||||
bool led_init_ok = led_strip_init(&led_strip_config);
|
||||
if (led_init_ok) {
|
||||
led_display = &led_strip_config;
|
||||
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d", strip.gpio, strip.length);
|
||||
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d on channek:%d", strip.gpio, strip.length, led_strip_config.rmt_channel);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "led_vu init failed");
|
||||
goto done;
|
||||
}
|
||||
|
||||
// reserver max memory for remote management systems
|
||||
rmt_set_mem_block_num(RMT_CHANNEL_1, 7);
|
||||
rmt_set_mem_block_num(led_strip_config.rmt_channel, 7);
|
||||
|
||||
led_vu_clear(led_display);
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
|
||||
#endif
|
||||
};
|
||||
|
||||
void register_optional_cmd(void) {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,16 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
|
||||
#endif
|
||||
};
|
||||
|
||||
extern void register_audio_config(void);
|
||||
extern void register_rotary_config(void);
|
||||
extern void register_ledvu_config(void);
|
||||
|
||||
void register_optional_cmd(void) {
|
||||
register_rotary_config();
|
||||
register_ledvu_config();
|
||||
register_audio_config();
|
||||
}
|
||||
|
||||
extern int squeezelite_main(int argc, char **argv);
|
||||
|
||||
static int launchsqueezelite(int argc, char **argv);
|
||||
|
||||
@@ -32,6 +32,8 @@ const char * desc_rotary= "Rotary Control";
|
||||
const char * desc_ledvu= "Led Strip Options";
|
||||
|
||||
extern const struct adac_s *dac_set[];
|
||||
extern void equalizer_set_loudness(uint8_t);
|
||||
extern void register_optional_cmd(void);
|
||||
|
||||
#define CODECS_BASE "flac|pcm|mp3|ogg"
|
||||
#if NO_FAAD
|
||||
@@ -140,6 +142,7 @@ static struct {
|
||||
} spdif_args;
|
||||
static struct {
|
||||
struct arg_str *jack_behavior;
|
||||
struct arg_int *loudness;
|
||||
struct arg_end *end;
|
||||
} audio_args;
|
||||
static struct {
|
||||
@@ -447,6 +450,30 @@ static int do_audio_cmd(int argc, char **argv){
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = ESP_OK; // suppress any error code that might have happened in a previous step
|
||||
|
||||
if(audio_args.loudness->count>0){
|
||||
char p[4]={0};
|
||||
int loudness_val = audio_args.loudness->ival[0];
|
||||
if( loudness_val < 0 || loudness_val>10){
|
||||
nerrors++;
|
||||
fprintf(f,"Invalid loudness value %d. Valid values are between 0 and 10.\n",loudness_val);
|
||||
}
|
||||
// it's not necessary to store loudness in NVS as set_loudness does it, but it does not hurt
|
||||
else {
|
||||
itoa(loudness_val,p,10);
|
||||
err = config_set_value(NVS_TYPE_STR, "loudness", p);
|
||||
}
|
||||
if(err!=ESP_OK){
|
||||
nerrors++;
|
||||
fprintf(f,"Error setting Loudness value %s. %s\n",p, esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
fprintf(f,"Loudness changed to %s\n",p);
|
||||
equalizer_set_loudness(loudness_val);
|
||||
}
|
||||
}
|
||||
|
||||
if(audio_args.jack_behavior->count>0){
|
||||
err = ESP_OK; // suppress any error code that might have happened in a previous step
|
||||
@@ -712,7 +739,7 @@ static int do_i2s_cmd(int argc, char **argv)
|
||||
cmd_send_messaging(argv[0],MESSAGING_ERROR,"DAC Configuration is locked on this platform\n");
|
||||
return 1;
|
||||
}
|
||||
strcpy(i2s_dac_pin.model, "I2S");
|
||||
|
||||
ESP_LOGD(TAG,"Processing i2s command %s with %d parameters",argv[0],argc);
|
||||
|
||||
esp_err_t err=ESP_OK;
|
||||
@@ -927,7 +954,10 @@ cJSON * audio_cb(){
|
||||
cJSON * values = cJSON_CreateObject();
|
||||
char * p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
|
||||
cJSON_AddStringToObject(values,"jack_behavior",(strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0)?"Headphones":"Subwoofer");
|
||||
FREE_AND_NULL(p);
|
||||
FREE_AND_NULL(p);
|
||||
p = config_alloc_get_default(NVS_TYPE_STR, "loudness", "0", 0);
|
||||
cJSON_AddStringToObject(values,"loudness",p);
|
||||
FREE_AND_NULL(p);
|
||||
return values;
|
||||
}
|
||||
cJSON * bt_source_cb(){
|
||||
@@ -1076,7 +1106,7 @@ static char * get_log_level_options(const char * longname){
|
||||
|
||||
// loop through dac_set and concatenate model name separated with |
|
||||
static char * get_dac_list(){
|
||||
const char * ES8388_MODEL_NAME = "ES8388|";
|
||||
const char * EXTRA_MODEL_NAMES = "ES8388|I2S";
|
||||
char * dac_list=NULL;
|
||||
size_t total_len=0;
|
||||
for(int i=0;dac_set[i];i++){
|
||||
@@ -1087,7 +1117,7 @@ static char * get_dac_list(){
|
||||
break;
|
||||
}
|
||||
}
|
||||
total_len+=strlen(ES8388_MODEL_NAME);
|
||||
total_len+=strlen(EXTRA_MODEL_NAMES);
|
||||
dac_list = malloc_init_external(total_len+1);
|
||||
if(dac_list){
|
||||
for(int i=0;dac_set[i];i++){
|
||||
@@ -1099,7 +1129,7 @@ static char * get_dac_list(){
|
||||
break;
|
||||
}
|
||||
}
|
||||
strcat(dac_list,ES8388_MODEL_NAME);
|
||||
strcat(dac_list,EXTRA_MODEL_NAMES);
|
||||
}
|
||||
return dac_list;
|
||||
}
|
||||
@@ -1270,7 +1300,7 @@ static void register_cspot_config(){
|
||||
}
|
||||
#endif
|
||||
static void register_i2s_config(void){
|
||||
i2s_args.model_name = arg_str1(NULL,"model_name",STR_OR_BLANK(get_dac_list()),"DAC Model Name");
|
||||
i2s_args.model_name = arg_str0(NULL,"model_name",STR_OR_BLANK(get_dac_list()),"DAC Model Name");
|
||||
i2s_args.clear = arg_lit0(NULL, "clear", "Clear configuration");
|
||||
i2s_args.clock = arg_int0(NULL,"clock","<n>","Clock GPIO. e.g. 33");
|
||||
i2s_args.wordselect = arg_int0(NULL,"wordselect","<n>","Word Select GPIO. e.g. 25");
|
||||
@@ -1311,7 +1341,7 @@ static void register_bt_source_config(void){
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
}
|
||||
|
||||
static void register_rotary_config(void){
|
||||
void register_rotary_config(void){
|
||||
rotary_args.rem = arg_rem("remark","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.\r\nEncoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay.");
|
||||
rotary_args.A = arg_int1(NULL,"A","gpio","A/DT gpio");
|
||||
rotary_args.B = arg_int1(NULL,"B","gpio","B/CLK gpio");
|
||||
@@ -1334,7 +1364,7 @@ static void register_rotary_config(void){
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
}
|
||||
|
||||
static void register_ledvu_config(void){
|
||||
void register_ledvu_config(void){
|
||||
ledvu_args.type = arg_str1(NULL,"type","<none>|WS2812","Led type (supports one rgb strip to display built in effects and allow remote control through 'dmx' messaging)");
|
||||
ledvu_args.length = arg_int1(NULL,"length","<1..255>","Strip length (1-255 supported)");
|
||||
ledvu_args.gpio = arg_int1(NULL,"gpio","gpio","Data pin");
|
||||
@@ -1352,8 +1382,10 @@ static void register_ledvu_config(void){
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
||||
}
|
||||
|
||||
static void register_audio_config(void){
|
||||
void register_audio_config(void){
|
||||
audio_args.jack_behavior = arg_str0("j", "jack_behavior","Headphones|Subwoofer","On supported DAC, determines the audio jack behavior. Selecting headphones will cause the external amp to be muted on insert, while selecting Subwoofer will keep the amp active all the time.");
|
||||
audio_args.loudness = arg_int0("l", "loudness","0-10","Sets the loudness level, from 0 to 10. 0 will disable the loudness completely.");
|
||||
audio_args.end = arg_end(6);
|
||||
audio_args.end = arg_end(6);
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = CFG_TYPE_AUDIO("general"),
|
||||
@@ -1433,8 +1465,6 @@ void register_config_cmd(void){
|
||||
#ifdef CONFIG_CSPOT_SINK
|
||||
register_cspot_config();
|
||||
#endif
|
||||
register_audio_config();
|
||||
// register_squeezelite_config();
|
||||
register_bt_source_config();
|
||||
if(!is_dac_config_locked()){
|
||||
register_i2s_config();
|
||||
@@ -1442,7 +1472,6 @@ void register_config_cmd(void){
|
||||
if(!is_spdif_config_locked()){
|
||||
register_spdif_config();
|
||||
}
|
||||
register_rotary_config();
|
||||
register_ledvu_config();
|
||||
register_optional_cmd();
|
||||
}
|
||||
|
||||
|
||||
@@ -147,13 +147,12 @@ static const i2c_db_t i2c_db[] = {
|
||||
{ .address = 0x0d, .description="AK8975"},
|
||||
{ .address = 0x0e, .description="MAG3110 AK8975 IST-8310"},
|
||||
{ .address = 0x0f, .description="AK8975"},
|
||||
{ .address = 0x10, .description="VEML7700 VML6075 VEML6075"},
|
||||
{ .address = 0x11, .description="Si4713 SAA5246 SAA5243P/K SAA5243P/L SAA5243P/E SAA5243P/H"},
|
||||
{ .address = 0x10, .description="VEML7700 VML6075 VEML6075 ES8388"},
|
||||
{ .address = 0x11, .description="Si4713 SAA5246 SAA5243P/K SAA5243P/L SAA5243P/E SAA5243P/H ES8388"},
|
||||
{ .address = 0x12, .description="SEN-17374"},
|
||||
{ .address = 0x13, .description="VCNL40x0 SEN-17374"},
|
||||
{ .address = 0x18, .description="MCP9808 LIS3DH LSM303 COM-15093"},
|
||||
{ .address = 0x19, .description="MCP9808 LIS3DH LSM303 COM-15093"},
|
||||
{ .address = 0x20, .description="ES8388"},
|
||||
{ .address = 0x1a, .description="AC101 MCP9808"},
|
||||
{ .address = 0x1b, .description="MCP9808"},
|
||||
{ .address = 0x1c, .description="MCP9808 MMA845x FXOS8700"},
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include "log_util.h"
|
||||
|
||||
#define RTSP_STACK_SIZE (8*1024)
|
||||
#define SEARCH_STACK_SIZE (3*1048)
|
||||
#define SEARCH_STACK_SIZE (3*1024)
|
||||
|
||||
typedef struct raop_ctx_s {
|
||||
#ifdef WIN32
|
||||
@@ -276,6 +276,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
|
||||
struct sockaddr_in addr;
|
||||
@@ -325,7 +326,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
// no command to send to remote or no remote found yet
|
||||
@@ -348,16 +349,19 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
|
||||
|
||||
asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
|
||||
kd_add(headers, "Active-Remote", ctx->active_remote.id);
|
||||
kd_add(headers, "Connection", "close");
|
||||
kd_add(headers, "Connection", "close");
|
||||
|
||||
buf = http_send(sock, method, headers);
|
||||
len = recv(sock, resp, 512, 0);
|
||||
if (len > 0) resp[len-1] = '\0';
|
||||
if (len > 0) resp[len-1] = '\0';
|
||||
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
|
||||
|
||||
NFREE(method);
|
||||
NFREE(buf);
|
||||
kd_free(headers);
|
||||
success = true;
|
||||
} else {
|
||||
kd_free(headers);
|
||||
LOG_INFO("[%p]: can't connect to remote for %s", ctx, command);
|
||||
}
|
||||
|
||||
free(command);
|
||||
@@ -622,15 +626,19 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
struct metadata_s metadata;
|
||||
dmap_settings settings = {
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
|
||||
NULL
|
||||
};
|
||||
NULL
|
||||
};
|
||||
|
||||
settings.ctx = &metadata;
|
||||
memset(&metadata, 0, sizeof(struct metadata_s));
|
||||
settings.ctx = &metadata;
|
||||
if (!dmap_parse(&settings, body, len)) {
|
||||
uint32_t timestamp = 0;
|
||||
if ((p = kd_lookup(headers, "RTP-Info")) != NULL) sscanf(p, "%*[^=]=%d", ×tamp);
|
||||
LOG_INFO("[%p]: received metadata (ts: %d)\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
|
||||
ctx, metadata.artist ? metadata.artist : "", metadata.album ? metadata.album : "",
|
||||
metadata.title ? metadata.title : "");
|
||||
ctx, timestamp, metadata.artist ? metadata.artist : "", metadata.album ? metadata.album : "",
|
||||
metadata.title ? metadata.title : "");
|
||||
success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title, timestamp);
|
||||
free_metadata(&metadata);
|
||||
}
|
||||
} else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && strcasestr(p, "image/jpeg")) {
|
||||
uint32_t timestamp = 0;
|
||||
@@ -680,7 +688,7 @@ void cleanup_rtsp(raop_ctx_t *ctx, bool abort) {
|
||||
#ifdef WIN32
|
||||
pthread_join(ctx->active_remote.thread, NULL);
|
||||
close_mDNS(ctx->active_remote.handle);
|
||||
#else
|
||||
#else
|
||||
// need to make sure no search is on-going and reclaim task memory
|
||||
ctx->active_remote.running = false;
|
||||
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
|
||||
|
||||
@@ -113,13 +113,19 @@ static bool cmd_handler(raop_event_t event, ...) {
|
||||
case RAOP_SETUP:
|
||||
actrls_set(controls, false, NULL, actrls_ir_action);
|
||||
displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY", true);
|
||||
displayer_artwork(NULL);
|
||||
break;
|
||||
case RAOP_PLAY:
|
||||
displayer_control(DISPLAYER_TIMER_RUN);
|
||||
break;
|
||||
case RAOP_FLUSH:
|
||||
displayer_control(DISPLAYER_TIMER_PAUSE);
|
||||
break;
|
||||
break;
|
||||
case RAOP_STALLED:
|
||||
raop_abort(raop);
|
||||
actrls_unset();
|
||||
displayer_control(DISPLAYER_SHUTDOWN);
|
||||
break;
|
||||
case RAOP_STOP:
|
||||
actrls_unset();
|
||||
displayer_control(DISPLAYER_SUSPEND);
|
||||
@@ -127,7 +133,6 @@ static bool cmd_handler(raop_event_t event, ...) {
|
||||
case RAOP_METADATA: {
|
||||
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
|
||||
displayer_metadata(artist, album, title);
|
||||
displayer_artwork(NULL);
|
||||
break;
|
||||
}
|
||||
case RAOP_ARTWORK: {
|
||||
@@ -170,7 +175,7 @@ static void raop_sink_start(nm_state_t state_id, int sub_state) {
|
||||
esp_netif_get_mac(netif, mac);
|
||||
cmd_handler_chain = raop_cbs.cmd;
|
||||
|
||||
LOG_INFO( "Starting Airplay for ip %s with servicename %s", inet_ntoa(ipInfo.ip.addr), sink_name);
|
||||
LOG_INFO( "starting Airplay for ip %s with servicename %s", inet_ntoa(ipInfo.ip.addr), sink_name);
|
||||
raop = raop_create(ipInfo.ip.addr, sink_name, mac, 0, cmd_handler, raop_cbs.data);
|
||||
free(sink_name);
|
||||
}
|
||||
@@ -191,8 +196,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
|
||||
*/
|
||||
void raop_disconnect(void) {
|
||||
LOG_INFO("forced disconnection");
|
||||
displayer_control(DISPLAYER_SHUTDOWN);
|
||||
// in case we can't communicate with AirPlay controller, abort session
|
||||
if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop);
|
||||
actrls_unset();
|
||||
if (!raop_cmd(raop, RAOP_STOP, NULL)) cmd_handler(RAOP_STALLED);
|
||||
else displayer_control(DISPLAYER_SHUTDOWN);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#define RAOP_SAMPLE_RATE 44100
|
||||
|
||||
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP,
|
||||
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, RAOP_STALLED,
|
||||
RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD,
|
||||
RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ static log_level *loglevel = &raop_loglevel;
|
||||
#define RTP_SYNC (0x01)
|
||||
#define NTP_SYNC (0x02)
|
||||
|
||||
#define RESEND_TO 200
|
||||
#define RESEND_TO 250
|
||||
|
||||
enum { DATA = 0, CONTROL, TIMING };
|
||||
|
||||
@@ -149,6 +149,7 @@ typedef struct rtp_s {
|
||||
struct alac_codec_s *alac_codec;
|
||||
int flush_seqno;
|
||||
bool playing;
|
||||
int stalled;
|
||||
raop_data_cb_t data_cb;
|
||||
raop_cmd_cb_t cmd_cb;
|
||||
} rtp_t;
|
||||
@@ -466,26 +467,27 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
|
||||
} else if (seq_order(ctx->ab_write, seqno)) {
|
||||
seq_t i;
|
||||
u32_t now;
|
||||
|
||||
// newer than expected
|
||||
if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
|
||||
// only get rtp latency-1 frames back (last one is seqno)
|
||||
LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
|
||||
ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
|
||||
}
|
||||
// this is a shitstorm, reset buffer
|
||||
LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
|
||||
ctx->ab_read = seqno;
|
||||
} else {
|
||||
// request re-send missed frames and evaluate resent date as a whole *after*
|
||||
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
|
||||
// resend date is after all requests have been sent
|
||||
u32_t now = gettime_ms();
|
||||
|
||||
// set expected timing of missed frames for buffer_push_packet and set last_resend date
|
||||
for (seq_t i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
|
||||
ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
|
||||
ctx->audio_buffer[BUFIDX(i)].last_resend = now;
|
||||
}
|
||||
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
}
|
||||
|
||||
// need to request re-send and adjust timing of gaps
|
||||
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
|
||||
ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
|
||||
ctx->audio_buffer[BUFIDX(i)].last_resend = now;
|
||||
}
|
||||
|
||||
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
} else if (seq_order(ctx->ab_read, seqno + 1)) {
|
||||
@@ -524,10 +526,9 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
static void buffer_push_packet(rtp_t *ctx) {
|
||||
abuf_t *curframe = NULL;
|
||||
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
|
||||
int i;
|
||||
|
||||
// not ready to play yet
|
||||
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
|
||||
// there is always at least one frame in the buffer
|
||||
do {
|
||||
@@ -573,10 +574,11 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
|
||||
|
||||
// each missing packet will be requested up to (latency_frames / 16) times
|
||||
for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
|
||||
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
|
||||
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
|
||||
if (!frame->ready && now - frame->last_resend > RESEND_TO) {
|
||||
rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
|
||||
// stop if one fails
|
||||
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break;
|
||||
frame->last_resend = now;
|
||||
}
|
||||
}
|
||||
@@ -613,7 +615,10 @@ static void rtp_thread_func(void *arg) {
|
||||
|
||||
FD_ZERO(&fds);
|
||||
for (i = 0; i < 3; i++) { FD_SET(ctx->rtp_sockets[i].sock, &fds); }
|
||||
|
||||
|
||||
if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) {
|
||||
if (ctx->stalled++ == 30*10) ctx->cmd_cb(RAOP_STALLED);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
@@ -631,6 +636,7 @@ static void rtp_thread_func(void *arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(plen <= MAX_PACKET);
|
||||
ctx->stalled = 0;
|
||||
|
||||
type = packet[1] & ~0x80;
|
||||
@@ -823,6 +829,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
|
||||
ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
|
||||
|
||||
if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
|
||||
LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include "soc/efuse_periph.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_common_internal.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/efuse.h"
|
||||
#endif
|
||||
#include "tools.h"
|
||||
#include "monitor.h"
|
||||
#include "messaging.h"
|
||||
@@ -1088,6 +1090,9 @@ gpio_entry_t * get_gpio_by_name(char * name,char * group, bool refresh){
|
||||
|
||||
|
||||
cJSON * get_psram_gpio_list(cJSON * list){
|
||||
cJSON * llist=list;
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
const char * psram_dev = "psram";
|
||||
const char * flash_dev = "flash";
|
||||
const char * clk = "clk";
|
||||
@@ -1096,7 +1101,6 @@ cJSON * get_psram_gpio_list(cJSON * list){
|
||||
const char * spid_sd1_io = "spid_sd1_io";
|
||||
const char * spiwp_sd3_io = "spiwp_sd3_io";
|
||||
const char * spihd_sd2_io = "spihd_sd2_io";
|
||||
cJSON * llist=list;
|
||||
|
||||
uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG);
|
||||
uint32_t pkg_ver = chip_ver & 0x7;
|
||||
@@ -1156,6 +1160,9 @@ cJSON * get_psram_gpio_list(cJSON * list){
|
||||
cJSON_AddItemToArray(list,get_gpio_entry(clk,flash_dev,EFUSE_SPICONFIG_RET_SPICLK(spiconfig),true));
|
||||
cJSON_AddItemToArray(list,get_gpio_entry(cs,flash_dev,EFUSE_SPICONFIG_RET_SPICS0(spiconfig),true));
|
||||
}
|
||||
#else
|
||||
#pragma message("need to add esp32-s3 specific SPIRAM GPIO config code")
|
||||
#endif
|
||||
return llist;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,8 +120,9 @@ static void ir_handler(uint16_t addr, uint16_t cmd) {
|
||||
*
|
||||
*/
|
||||
static void set_ir_gpio(int gpio, char *value) {
|
||||
if (!strcasecmp(value, "ir") ) {
|
||||
create_infrared(gpio, ir_handler);
|
||||
if (strcasestr(value, "ir")) {
|
||||
if (strcasestr(value, "rc5")) create_infrared(gpio, ir_handler, IR_RC5);
|
||||
else create_infrared(gpio, ir_handler, IR_NEC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -423,9 +423,9 @@ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handle
|
||||
/****************************************************************************************
|
||||
* Create Infrared
|
||||
*/
|
||||
bool create_infrared(int gpio, infrared_handler handler) {
|
||||
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode) {
|
||||
// initialize IR infrastructure
|
||||
infrared_init(&infrared.rb, gpio);
|
||||
infrared_init(&infrared.rb, gpio, mode);
|
||||
infrared.handler = handler;
|
||||
|
||||
// join the queue set
|
||||
|
||||
@@ -35,4 +35,4 @@ typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
|
||||
|
||||
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
|
||||
|
||||
bool create_infrared(int gpio, infrared_handler handler);
|
||||
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);
|
||||
|
||||
@@ -17,6 +17,7 @@ extern int i2c_system_port;
|
||||
extern int i2c_system_speed;
|
||||
extern int spi_system_host;
|
||||
extern int spi_system_dc_gpio;
|
||||
extern int rmt_system_base_channel;
|
||||
typedef struct {
|
||||
int timer, base_channel, max;
|
||||
} pwm_system_t;
|
||||
|
||||
@@ -537,7 +537,7 @@ static esp_err_t mpr121_init(gpio_exp_t* self) {
|
||||
{ 0x57, 0x28 }, { 0x58, 0x14 }, /* ELE11: Touch Threshold, Release Threshold */
|
||||
|
||||
{ 0x5e, 0xcc }, /* ECR - must be set last */
|
||||
{0, 0}
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
esp_err_t err = 0;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,164 +14,485 @@
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "globdefs.h"
|
||||
#include "infrared.h"
|
||||
|
||||
static const char* TAG = "IR";
|
||||
|
||||
#define RMT_RX_ACTIVE_LEVEL 0 /*!< If we connect with a IR receiver, the data is active low */
|
||||
#define IR_TOOLS_FLAGS_PROTO_EXT (1 << 0) /*!< Enable Extended IR protocol */
|
||||
#define IR_TOOLS_FLAGS_INVERSE (1 << 1) /*!< Inverse the IR signal, i.e. take high level as low, and vice versa */
|
||||
|
||||
#define RMT_RX_CHANNEL 0 /*!< RMT channel for receiver */
|
||||
#define RMT_CLK_DIV 100 /*!< RMT counter clock divider */
|
||||
#define RMT_TICK_10_US (80000000/RMT_CLK_DIV/100000) /*!< RMT counter value for 10 us.(Source clock is APB clock) */
|
||||
/**
|
||||
* @brief IR device type
|
||||
*
|
||||
*/
|
||||
typedef void *ir_dev_t;
|
||||
|
||||
#define NEC_HEADER_HIGH_US 9000 /*!< NEC protocol header: positive 9ms */
|
||||
#define NEC_HEADER_LOW_US 4500 /*!< NEC protocol header: negative 4.5ms*/
|
||||
#define NEC_BIT_ONE_HIGH_US 560 /*!< NEC protocol data bit 1: positive 0.56ms */
|
||||
#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US) /*!< NEC protocol data bit 1: negative 1.69ms */
|
||||
#define NEC_BIT_ZERO_HIGH_US 560 /*!< NEC protocol data bit 0: positive 0.56ms */
|
||||
#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US) /*!< NEC protocol data bit 0: negative 0.56ms */
|
||||
#define NEC_BIT_MARGIN 150 /*!< NEC parse margin time */
|
||||
/**
|
||||
* @brief IR parser type
|
||||
*
|
||||
*/
|
||||
typedef struct ir_parser_s ir_parser_t;
|
||||
|
||||
#define NEC_ITEM_DURATION(d) ((d & 0x7fff)*10/RMT_TICK_10_US) /*!< Parse duration time from memory register value */
|
||||
#define NEC_DATA_ITEM_NUM 34 /*!< NEC code item number: header + 32bit data + end */
|
||||
#define rmt_item32_tIMEOUT_US 9500 /*!< RMT receiver timeout value(us) */
|
||||
/**
|
||||
* @brief Type definition of IR parser
|
||||
*
|
||||
*/
|
||||
struct ir_parser_s {
|
||||
/**
|
||||
* @brief Input raw data to IR parser
|
||||
*
|
||||
* @param[in] parser: Handle of IR parser
|
||||
* @param[in] raw_data: Raw data which need decoding by IR parser
|
||||
* @param[in] length: Length of raw data
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Input raw data successfully
|
||||
* - ESP_ERR_INVALID_ARG: Input raw data failed because of invalid argument
|
||||
* - ESP_FAIL: Input raw data failed because some other error occurred
|
||||
*/
|
||||
esp_err_t (*input)(ir_parser_t *parser, void *raw_data, uint32_t length);
|
||||
|
||||
/**
|
||||
* @brief Get the scan code after decoding of raw data
|
||||
*
|
||||
* @param[in] parser: Handle of IR parser
|
||||
* @param[out] address: Address of the scan code
|
||||
* @param[out] command: Command of the scan code
|
||||
* @param[out] repeat: Indicate if it's a repeat code
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Get scan code successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get scan code failed because of invalid arguments
|
||||
* - ESP_FAIL: Get scan code failed because some error occurred
|
||||
*/
|
||||
esp_err_t (*get_scan_code)(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat);
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ir_dev_t dev_hdl; /*!< IR device handle */
|
||||
uint32_t flags; /*!< Flags for IR parser, different flags will enable different features */
|
||||
uint32_t margin_us; /*!< Timing parameter, indicating the tolerance to environment noise */
|
||||
} ir_parser_config_t;
|
||||
|
||||
#define IR_PARSER_DEFAULT_CONFIG(dev) \
|
||||
{ \
|
||||
.dev_hdl = dev, \
|
||||
.flags = 0, \
|
||||
.margin_us = 200, \
|
||||
}
|
||||
|
||||
ir_parser_t *ir_parser = NULL;
|
||||
|
||||
#define RMT_CHECK(a, str, goto_tag, ret_value, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (!(a)) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
ret = ret_value; \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* NEC protocol
|
||||
****************************************************************************************/
|
||||
|
||||
#define NEC_DATA_FRAME_RMT_WORDS (34)
|
||||
#define NEC_REPEAT_FRAME_RMT_WORDS (2)
|
||||
#define NEC_LEADING_CODE_HIGH_US (9000)
|
||||
#define NEC_LEADING_CODE_LOW_US (4500)
|
||||
#define NEC_PAYLOAD_ONE_HIGH_US (560)
|
||||
#define NEC_PAYLOAD_ONE_LOW_US (1690)
|
||||
#define NEC_PAYLOAD_ZERO_HIGH_US (560)
|
||||
#define NEC_PAYLOAD_ZERO_LOW_US (560)
|
||||
#define NEC_REPEAT_CODE_HIGH_US (9000)
|
||||
#define NEC_REPEAT_CODE_LOW_US (2250)
|
||||
#define NEC_ENDING_CODE_HIGH_US (560)
|
||||
|
||||
|
||||
typedef struct {
|
||||
ir_parser_t parent;
|
||||
uint32_t flags;
|
||||
uint32_t leading_code_high_ticks;
|
||||
uint32_t leading_code_low_ticks;
|
||||
uint32_t repeat_code_high_ticks;
|
||||
uint32_t repeat_code_low_ticks;
|
||||
uint32_t payload_logic0_high_ticks;
|
||||
uint32_t payload_logic0_low_ticks;
|
||||
uint32_t payload_logic1_high_ticks;
|
||||
uint32_t payload_logic1_low_ticks;
|
||||
uint32_t margin_ticks;
|
||||
rmt_item32_t *buffer;
|
||||
uint32_t cursor;
|
||||
uint32_t last_address;
|
||||
uint32_t last_command;
|
||||
bool repeat;
|
||||
bool inverse;
|
||||
} nec_parser_t;
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_check_in_range(int duration_ticks, int target_us, int margin_us) {
|
||||
if(( NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
|
||||
&& ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
|
||||
return true;
|
||||
static inline bool nec_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) {
|
||||
return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_parse_head(nec_parser_t *nec_parser) {
|
||||
nec_parser->cursor = 0;
|
||||
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
|
||||
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
|
||||
nec_check_in_range(item.duration0, nec_parser->leading_code_high_ticks, nec_parser->margin_ticks) &&
|
||||
nec_check_in_range(item.duration1, nec_parser->leading_code_low_ticks, nec_parser->margin_ticks);
|
||||
nec_parser->cursor += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_parse_logic0(nec_parser_t *nec_parser) {
|
||||
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
|
||||
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
|
||||
nec_check_in_range(item.duration0, nec_parser->payload_logic0_high_ticks, nec_parser->margin_ticks) &&
|
||||
nec_check_in_range(item.duration1, nec_parser->payload_logic0_low_ticks, nec_parser->margin_ticks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_parse_logic1(nec_parser_t *nec_parser) {
|
||||
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
|
||||
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
|
||||
nec_check_in_range(item.duration0, nec_parser->payload_logic1_high_ticks, nec_parser->margin_ticks) &&
|
||||
nec_check_in_range(item.duration1, nec_parser->payload_logic1_low_ticks, nec_parser->margin_ticks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t nec_parse_logic(ir_parser_t *parser, bool *logic) {
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
bool logic_value = false;
|
||||
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
|
||||
if (nec_parse_logic0(nec_parser)) {
|
||||
logic_value = false;
|
||||
ret = ESP_OK;
|
||||
} else if (nec_parse_logic1(nec_parser)) {
|
||||
logic_value = true;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
if (ret == ESP_OK) {
|
||||
*logic = logic_value;
|
||||
}
|
||||
nec_parser->cursor += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_parse_repeat_frame(nec_parser_t *nec_parser) {
|
||||
nec_parser->cursor = 0;
|
||||
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
|
||||
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
|
||||
nec_check_in_range(item.duration0, nec_parser->repeat_code_high_ticks, nec_parser->margin_ticks) &&
|
||||
nec_check_in_range(item.duration1, nec_parser->repeat_code_low_ticks, nec_parser->margin_ticks);
|
||||
nec_parser->cursor += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t nec_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
|
||||
RMT_CHECK(raw_data, "input data can't be null", err, ESP_ERR_INVALID_ARG);
|
||||
nec_parser->buffer = raw_data;
|
||||
// Data Frame costs 34 items and Repeat Frame costs 2 items
|
||||
if (length == NEC_DATA_FRAME_RMT_WORDS) {
|
||||
nec_parser->repeat = false;
|
||||
} else if (length == NEC_REPEAT_FRAME_RMT_WORDS) {
|
||||
nec_parser->repeat = true;
|
||||
} else {
|
||||
return false;
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
return ret;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_header_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_bit_one_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_bit_zero_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static int nec_parse_items(rmt_item32_t* item, int item_num, uint16_t* addr, uint16_t* data) {
|
||||
int w_len = item_num;
|
||||
if(w_len < NEC_DATA_ITEM_NUM) {
|
||||
return -1;
|
||||
}
|
||||
int i = 0, j = 0;
|
||||
if(!nec_header_if(item++)) {
|
||||
return -1;
|
||||
}
|
||||
uint16_t addr_t = 0;
|
||||
for(j = 15; j >= 0; j--) {
|
||||
if(nec_bit_one_if(item)) {
|
||||
addr_t |= (1 << j);
|
||||
} else if(nec_bit_zero_if(item)) {
|
||||
addr_t |= (0 << j);
|
||||
} else {
|
||||
return -1;
|
||||
static esp_err_t nec_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) {
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
uint32_t addr = 0;
|
||||
uint32_t cmd = 0;
|
||||
bool logic_value = false;
|
||||
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
|
||||
if (nec_parser->repeat) {
|
||||
if (nec_parse_repeat_frame(nec_parser)) {
|
||||
*address = nec_parser->last_address;
|
||||
*command = nec_parser->last_command;
|
||||
*repeat = true;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
item++;
|
||||
i++;
|
||||
}
|
||||
uint16_t data_t = 0;
|
||||
for(j = 15; j >= 0; j--) {
|
||||
if(nec_bit_one_if(item)) {
|
||||
data_t |= (1 << j);
|
||||
} else if(nec_bit_zero_if(item)) {
|
||||
data_t |= (0 << j);
|
||||
} else {
|
||||
return -1;
|
||||
} else {
|
||||
if (nec_parse_head(nec_parser)) {
|
||||
// for the forgetful, need to do a bitreverse
|
||||
for (int i = 15; i >= 0; i--) {
|
||||
if (nec_parse_logic(parser, &logic_value) == ESP_OK) {
|
||||
addr |= (logic_value << i);
|
||||
}
|
||||
}
|
||||
for (int i = 15; i >= 0; i--) {
|
||||
if (nec_parse_logic(parser, &logic_value) == ESP_OK) {
|
||||
cmd |= (logic_value << i);
|
||||
}
|
||||
}
|
||||
*address = addr;
|
||||
*command = cmd;
|
||||
*repeat = false;
|
||||
// keep it as potential repeat code
|
||||
nec_parser->last_address = addr;
|
||||
nec_parser->last_command = cmd;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
item++;
|
||||
i++;
|
||||
}
|
||||
*addr = addr_t;
|
||||
*data = data_t;
|
||||
return i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
ir_parser_t *ir_parser_rmt_new_nec(const ir_parser_config_t *config) {
|
||||
ir_parser_t *ret = NULL;
|
||||
nec_parser_t *nec_parser = calloc(1, sizeof(nec_parser_t));
|
||||
|
||||
nec_parser->flags = config->flags;
|
||||
if (config->flags & IR_TOOLS_FLAGS_INVERSE) {
|
||||
nec_parser->inverse = true;
|
||||
}
|
||||
|
||||
uint32_t counter_clk_hz = 0;
|
||||
RMT_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK,
|
||||
"get rmt counter clock failed", err, NULL);
|
||||
float ratio = (float)counter_clk_hz / 1e6;
|
||||
nec_parser->leading_code_high_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_HIGH_US);
|
||||
nec_parser->leading_code_low_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_LOW_US);
|
||||
nec_parser->repeat_code_high_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_HIGH_US);
|
||||
nec_parser->repeat_code_low_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_LOW_US);
|
||||
nec_parser->payload_logic0_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_HIGH_US);
|
||||
nec_parser->payload_logic0_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_LOW_US);
|
||||
nec_parser->payload_logic1_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_HIGH_US);
|
||||
nec_parser->payload_logic1_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_LOW_US);
|
||||
nec_parser->margin_ticks = (uint32_t)(ratio * config->margin_us);
|
||||
nec_parser->parent.input = nec_parser_input;
|
||||
nec_parser->parent.get_scan_code = nec_parser_get_scan_code;
|
||||
return &nec_parser->parent;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* RC5 protocol
|
||||
****************************************************************************************/
|
||||
|
||||
#define RC5_MAX_FRAME_RMT_WORDS (14) // S1+S2+T+ADDR(5)+CMD(6)
|
||||
#define RC5_PULSE_DURATION_US (889)
|
||||
|
||||
typedef struct {
|
||||
ir_parser_t parent;
|
||||
uint32_t flags;
|
||||
uint32_t pulse_duration_ticks;
|
||||
uint32_t margin_ticks;
|
||||
rmt_item32_t *buffer;
|
||||
uint32_t buffer_len;
|
||||
uint32_t last_command;
|
||||
uint32_t last_address;
|
||||
bool last_t_bit;
|
||||
} rc5_parser_t;
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static inline bool rc5_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) {
|
||||
return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t rc5_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent);
|
||||
rc5_parser->buffer = raw_data;
|
||||
rc5_parser->buffer_len = length;
|
||||
if (length > RC5_MAX_FRAME_RMT_WORDS) {
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static inline bool rc5_duration_one_unit(rc5_parser_t *rc5_parser, uint32_t duration) {
|
||||
return (duration < (rc5_parser->pulse_duration_ticks + rc5_parser->margin_ticks)) &&
|
||||
(duration > (rc5_parser->pulse_duration_ticks - rc5_parser->margin_ticks));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static inline bool rc5_duration_two_unit(rc5_parser_t *rc5_parser, uint32_t duration) {
|
||||
return (duration < (rc5_parser->pulse_duration_ticks * 2 + rc5_parser->margin_ticks)) &&
|
||||
(duration > (rc5_parser->pulse_duration_ticks * 2 - rc5_parser->margin_ticks));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t rc5_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) {
|
||||
esp_err_t ret = ESP_FAIL;
|
||||
uint32_t parse_result = 0; // 32 bit is enough to hold the parse result of one RC5 frame
|
||||
uint32_t addr = 0;
|
||||
uint32_t cmd = 0;
|
||||
bool s1 = true;
|
||||
bool s2 = true;
|
||||
bool t = false;
|
||||
bool exchange = false;
|
||||
rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent);
|
||||
for (int i = 0; i < rc5_parser->buffer_len; i++) {
|
||||
if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration0)) {
|
||||
parse_result <<= 1;
|
||||
parse_result |= exchange;
|
||||
if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration1)) {
|
||||
exchange = !exchange;
|
||||
}
|
||||
} else if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration0)) {
|
||||
parse_result <<= 1;
|
||||
parse_result |= rc5_parser->buffer[i].level0;
|
||||
parse_result <<= 1;
|
||||
parse_result |= !rc5_parser->buffer[i].level0;
|
||||
if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration1)) {
|
||||
exchange = !exchange;
|
||||
}
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if (!(rc5_parser->flags & IR_TOOLS_FLAGS_INVERSE)) {
|
||||
parse_result = ~parse_result;
|
||||
}
|
||||
s1 = ((parse_result & 0x2000) >> 13) & 0x01;
|
||||
s2 = ((parse_result & 0x1000) >> 12) & 0x01;
|
||||
t = ((parse_result & 0x800) >> 11) & 0x01;
|
||||
// Check S1, must be 1
|
||||
if (s1) {
|
||||
if (!(rc5_parser->flags & IR_TOOLS_FLAGS_PROTO_EXT) && !s2) {
|
||||
// Not standard RC5 protocol, but S2 is 0
|
||||
goto out;
|
||||
}
|
||||
addr = (parse_result & 0x7C0) >> 6;
|
||||
cmd = (parse_result & 0x3F);
|
||||
if (!s2) {
|
||||
cmd |= 1 << 6;
|
||||
}
|
||||
*repeat = (t == rc5_parser->last_t_bit && addr == rc5_parser->last_address && cmd == rc5_parser->last_command);
|
||||
*address = addr;
|
||||
*command = cmd;
|
||||
rc5_parser->last_address = addr;
|
||||
rc5_parser->last_command = cmd;
|
||||
rc5_parser->last_t_bit = t;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
ir_parser_t *ir_parser_rmt_new_rc5(const ir_parser_config_t *config) {
|
||||
ir_parser_t *ret = NULL;
|
||||
rc5_parser_t *rc5_parser = calloc(1, sizeof(rc5_parser_t));
|
||||
|
||||
rc5_parser->flags = config->flags;
|
||||
|
||||
uint32_t counter_clk_hz = 0;
|
||||
RMT_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK,
|
||||
"get rmt counter clock failed", err, NULL);
|
||||
float ratio = (float)counter_clk_hz / 1e6;
|
||||
rc5_parser->pulse_duration_ticks = (uint32_t)(ratio * RC5_PULSE_DURATION_US);
|
||||
rc5_parser->margin_ticks = (uint32_t)(ratio * config->margin_us);
|
||||
rc5_parser->parent.input = rc5_parser_input;
|
||||
rc5_parser->parent.get_scan_code = rc5_parser_get_scan_code;
|
||||
return &rc5_parser->parent;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
|
||||
size_t rx_size = 0;
|
||||
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 10 / portTICK_RATE_MS);
|
||||
|
||||
|
||||
if (item) {
|
||||
uint16_t addr, cmd;
|
||||
int offset = 0;
|
||||
|
||||
while (1) {
|
||||
// parse data value from ringbuffer.
|
||||
int res = nec_parse_items(item + offset, rx_size / 4 - offset, &addr, &cmd);
|
||||
if (res > 0) {
|
||||
offset += res + 1;
|
||||
handler(addr, cmd);
|
||||
ESP_LOGD(TAG, "RMT RCV --- addr: 0x%04x cmd: 0x%04x", addr, cmd);
|
||||
} else break;
|
||||
uint32_t addr, cmd;
|
||||
bool repeat = false;
|
||||
bool decoded = false;
|
||||
|
||||
rx_size /= 4; // one RMT = 4 Bytes
|
||||
|
||||
if (ir_parser->input(ir_parser, item, rx_size) == ESP_OK) {
|
||||
if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) {
|
||||
decoded = true;
|
||||
handler(addr, cmd);
|
||||
ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if we have not decoded data but lenght is reasonnable, dump it
|
||||
if (!decoded && rx_size > RC5_MAX_FRAME_RMT_WORDS) {
|
||||
ESP_LOGI(TAG, "can't decode IR signal of len %d", rx_size);
|
||||
ESP_LOG_BUFFER_HEX(TAG, item, rx_size * 4);
|
||||
}
|
||||
|
||||
// after parsing the data, return spaces to ringbuffer.
|
||||
vRingbufferReturnItem(rb, (void*) item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio) {
|
||||
rmt_config_t rmt_rx;
|
||||
|
||||
ESP_LOGI(TAG, "Starting Infrared Receiver on gpio %d", gpio);
|
||||
|
||||
// initialize RMT driver
|
||||
rmt_rx.channel = RMT_RX_CHANNEL;
|
||||
rmt_rx.gpio_num = gpio;
|
||||
rmt_rx.clk_div = RMT_CLK_DIV;
|
||||
rmt_rx.mem_block_num = 1;
|
||||
rmt_rx.rmt_mode = RMT_MODE_RX;
|
||||
rmt_rx.rx_config.filter_en = true;
|
||||
rmt_rx.rx_config.filter_ticks_thresh = 100;
|
||||
rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
|
||||
rmt_config(&rmt_rx);
|
||||
rmt_driver_install(rmt_rx.channel, 1000, 0);
|
||||
|
||||
// get RMT RX ringbuffer
|
||||
rmt_get_ringbuf_handle(RMT_RX_CHANNEL, rb);
|
||||
rmt_rx_start(RMT_RX_CHANNEL, 1);
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) {
|
||||
int rmt_channel = rmt_system_base_channel++;
|
||||
rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(gpio, rmt_channel);
|
||||
rmt_config(&rmt_rx_config);
|
||||
rmt_driver_install(rmt_rx_config.channel, 1000, 0);
|
||||
ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t) rmt_rx_config.channel);
|
||||
ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version)
|
||||
|
||||
ir_parser = (mode == IR_NEC) ? ir_parser_rmt_new_nec(&ir_parser_config) : ir_parser_rmt_new_rc5(&ir_parser_config);
|
||||
|
||||
// get RMT RX ringbuffer
|
||||
rmt_get_ringbuf_handle(rmt_channel, rb);
|
||||
rmt_rx_start(rmt_channel, 1);
|
||||
|
||||
ESP_LOGI(TAG, "Starting Infrared Receiver mode %s on gpio %d and channel %d", mode == IR_NEC ? "nec" : "rc5", gpio, rmt_channel);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
|
||||
|
||||
typedef enum {IR_NEC, IR_RC5} infrared_mode_t;
|
||||
typedef void (*infrared_handler)(uint16_t addr, uint16_t cmd);
|
||||
|
||||
void infrared_receive(RingbufHandle_t rb, infrared_handler handler);
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio);
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "platform_config.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "led.h"
|
||||
@@ -27,88 +28,134 @@
|
||||
#define MAX_LED 8
|
||||
#define BLOCKTIME 10 // up to portMAX_DELAY
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#else
|
||||
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
|
||||
#endif
|
||||
|
||||
static const char *TAG = "led";
|
||||
|
||||
#define RMT_CLK (40/2)
|
||||
|
||||
static int8_t led_rmt_channel = -1;
|
||||
static uint32_t scale24(uint32_t bright, uint8_t);
|
||||
|
||||
static const struct rmt_led_param_s {
|
||||
led_type_t type;
|
||||
uint8_t bits;
|
||||
// number of ticks in nanoseconds converted in RMT_CLK ticks
|
||||
rmt_item32_t bit_0;
|
||||
rmt_item32_t bit_1;
|
||||
uint32_t green, red;
|
||||
uint32_t (*scale)(uint32_t, uint8_t);
|
||||
} rmt_led_param[] = {
|
||||
{ LED_WS2812, 24, {{{350 / RMT_CLK, 1, 1000 / RMT_CLK, 0}}}, {{{1000 / RMT_CLK, 1, 350 / RMT_CLK, 0}}}, 0xff0000, 0x00ff00, scale24 },
|
||||
{ .type = -1 } };
|
||||
|
||||
static EXT_RAM_ATTR struct led_s {
|
||||
gpio_num_t gpio;
|
||||
bool on;
|
||||
int onstate;
|
||||
uint32_t color;
|
||||
int ontime, offtime;
|
||||
int pwm;
|
||||
int bright;
|
||||
int channel;
|
||||
const struct rmt_led_param_s *rmt;
|
||||
int pushedon, pushedoff;
|
||||
bool pushed;
|
||||
TimerHandle_t timer;
|
||||
} leds[MAX_LED];
|
||||
|
||||
// can't use EXT_RAM_ATTR for initialized structure
|
||||
static struct {
|
||||
static struct led_config_s {
|
||||
int gpio;
|
||||
int active;
|
||||
int pwm;
|
||||
} green = { .gpio = CONFIG_LED_GREEN_GPIO, .active = 0, .pwm = -1 },
|
||||
red = { .gpio = CONFIG_LED_RED_GPIO, .active = 0, .pwm = -1 };
|
||||
|
||||
int color;
|
||||
int bright;
|
||||
led_type_t type;
|
||||
} green = { .gpio = CONFIG_LED_GREEN_GPIO, .color = 0, .bright = -1, .type = LED_GPIO },
|
||||
red = { .gpio = CONFIG_LED_RED_GPIO, .color = 0, .bright = -1, .type = LED_GPIO };
|
||||
|
||||
static int led_max = 2;
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void set_level(struct led_s *led, bool on) {
|
||||
if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_x(led->gpio, on ? led->onstate : !led->onstate);
|
||||
else {
|
||||
ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
|
||||
ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
|
||||
}
|
||||
static uint32_t scale24(uint32_t color, uint8_t scale) {
|
||||
uint32_t scaled = (((color & 0xff0000) >> 16) * scale / 100) << 16;
|
||||
scaled |= (((color & 0xff00) >> 8) * scale / 100) << 8;
|
||||
scaled |= (color & 0xff) * scale / 100;
|
||||
return scaled;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void set_level(struct led_s *led, bool on) {
|
||||
if (led->rmt) {
|
||||
uint32_t data = on ? led->rmt->scale(led->color, led->bright) : 0;
|
||||
uint32_t mask = 1 << (led->rmt->bits - 1);
|
||||
rmt_item32_t buffer[led->rmt->bits];
|
||||
for (uint32_t bit = 0; bit < led->rmt->bits; bit++) {
|
||||
uint32_t set = data & mask;
|
||||
buffer[bit] = set ? led->rmt->bit_1 : led->rmt->bit_0;
|
||||
mask >>= 1;
|
||||
}
|
||||
rmt_write_items(led->channel, buffer, led->rmt->bits, false);
|
||||
} else if (led->bright < 0 || led->gpio >= GPIO_NUM_MAX) {
|
||||
gpio_set_level_x(led->gpio, on ? led->color : !led->color);
|
||||
} else {
|
||||
ledc_set_duty(LEDC_SPEED_MODE, led->channel, on ? led->bright : (led->color ? 0 : pwm_system.max));
|
||||
ledc_update_duty(LEDC_SPEED_MODE, led->channel);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void vCallbackFunction( TimerHandle_t xTimer ) {
|
||||
struct led_s *led = (struct led_s*) pvTimerGetTimerID (xTimer);
|
||||
|
||||
|
||||
if (!led->timer) return;
|
||||
|
||||
|
||||
led->on = !led->on;
|
||||
ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (pwm:%d)", led->gpio, led->on, led->pwm);
|
||||
ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (bright:%d)", led->gpio, led->on, led->bright);
|
||||
set_level(led, led->on);
|
||||
|
||||
|
||||
// was just on for a while
|
||||
if (!led->on && led->offtime == -1) return;
|
||||
|
||||
|
||||
// regular blinking
|
||||
xTimerChangePeriod(xTimer, (led->on ? led->ontime : led->offtime) / portTICK_RATE_MS, BLOCKTIME);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
|
||||
if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false;
|
||||
|
||||
|
||||
ESP_LOGD(TAG,"led_blink_core %d on:%d off:%d, pushed:%u", idx, ontime, offtime, pushed);
|
||||
if (leds[idx].timer) {
|
||||
// normal requests waits if a pop is pending
|
||||
if (!pushed && leds[idx].pushed) {
|
||||
leds[idx].pushedon = ontime;
|
||||
leds[idx].pushedoff = offtime;
|
||||
leds[idx].pushedon = ontime;
|
||||
leds[idx].pushedoff = offtime;
|
||||
return true;
|
||||
}
|
||||
xTimerStop(leds[idx].timer, BLOCKTIME);
|
||||
}
|
||||
|
||||
|
||||
// save current state if not already pushed
|
||||
if (!leds[idx].pushed) {
|
||||
leds[idx].pushedon = leds[idx].ontime;
|
||||
leds[idx].pushedoff = leds[idx].offtime;
|
||||
leds[idx].pushedoff = leds[idx].offtime;
|
||||
leds[idx].pushed = pushed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// then set new one
|
||||
leds[idx].ontime = ontime;
|
||||
leds[idx].offtime = offtime;
|
||||
|
||||
leds[idx].offtime = offtime;
|
||||
|
||||
if (ontime == 0) {
|
||||
ESP_LOGD(TAG,"led %d, setting reverse level", idx);
|
||||
set_level(leds + idx, false);
|
||||
@@ -126,39 +173,44 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
|
||||
ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio);
|
||||
if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool led_brightness(int idx, int pwm) {
|
||||
if (pwm > 100) pwm = 100;
|
||||
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
|
||||
if (!leds[idx].onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
|
||||
|
||||
ledc_set_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel, leds[idx].pwm);
|
||||
ledc_update_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool led_brightness(int idx, int bright) {
|
||||
if (bright > 100) bright = 100;
|
||||
|
||||
if (leds[idx].rmt) {
|
||||
leds[idx].bright = bright;
|
||||
} else {
|
||||
leds[idx].bright = pwm_system.max * powf(bright / 100.0, 3);
|
||||
if (!leds[idx].color) leds[idx].bright = pwm_system.max - leds[idx].bright;
|
||||
|
||||
ledc_set_duty(LEDC_SPEED_MODE, leds[idx].channel, leds[idx].bright);
|
||||
ledc_update_duty(LEDC_SPEED_MODE, leds[idx].channel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
bool led_unpush(int idx) {
|
||||
if (!leds[idx].gpio || leds[idx].gpio<0) return false;
|
||||
|
||||
|
||||
led_blink_core(idx, leds[idx].pushedon, leds[idx].pushedoff, true);
|
||||
leds[idx].pushed = false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
int led_allocate(void) {
|
||||
if (led_max < MAX_LED) return led_max++;
|
||||
@@ -166,83 +218,115 @@ int led_allocate(void) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
|
||||
bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type) {
|
||||
if (gpio < 0) {
|
||||
ESP_LOGW(TAG,"LED GPIO -1 ignored");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s", idx, gpio, onstate>0?"On":"Off");
|
||||
if (idx >= MAX_LED) return false;
|
||||
|
||||
leds[idx].gpio = gpio;
|
||||
leds[idx].onstate = onstate;
|
||||
leds[idx].pwm = -1;
|
||||
|
||||
if (pwm < 0 || gpio >= GPIO_NUM_MAX) {
|
||||
if (idx >= MAX_LED) return false;
|
||||
|
||||
if (bright > 100) bright = 100;
|
||||
|
||||
leds[idx].gpio = gpio;
|
||||
leds[idx].color = color;
|
||||
leds[idx].rmt = NULL;
|
||||
leds[idx].bright = -1;
|
||||
|
||||
if (type != LED_GPIO) {
|
||||
// first make sure we have a known addressable led
|
||||
for (const struct rmt_led_param_s *p = rmt_led_param; !leds[idx].rmt && p->type >= 0; p++) if (p->type == type) leds[idx].rmt = p;
|
||||
if (!leds[idx].rmt) return false;
|
||||
|
||||
if (led_rmt_channel < 0) led_rmt_channel = rmt_system_base_channel++;
|
||||
leds[idx].channel = led_rmt_channel;
|
||||
leds[idx].bright = bright > 0 ? bright : 100;
|
||||
|
||||
// set counter clock to 40MHz
|
||||
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, leds[idx].channel);
|
||||
config.clk_div = 2;
|
||||
|
||||
rmt_config(&config);
|
||||
rmt_driver_install(config.channel, 0, 0);
|
||||
} else if (bright < 0 || gpio >= GPIO_NUM_MAX) {
|
||||
gpio_pad_select_gpio_x(gpio);
|
||||
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
|
||||
} else {
|
||||
} else {
|
||||
leds[idx].channel = pwm_system.base_channel++;
|
||||
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
|
||||
if (!onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
|
||||
|
||||
leds[idx].bright = pwm_system.max * powf(bright / 100.0, 3);
|
||||
if (!color) leds[idx].bright = pwm_system.max - leds[idx].bright;
|
||||
|
||||
ledc_channel_config_t ledc_channel = {
|
||||
.channel = leds[idx].channel,
|
||||
.duty = leds[idx].pwm,
|
||||
.duty = leds[idx].bright,
|
||||
.gpio_num = gpio,
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
.speed_mode = LEDC_SPEED_MODE,
|
||||
.hpoint = 0,
|
||||
.timer_sel = pwm_system.timer,
|
||||
};
|
||||
|
||||
|
||||
ledc_channel_config(&ledc_channel);
|
||||
}
|
||||
|
||||
|
||||
set_level(leds + idx, false);
|
||||
ESP_LOGD(TAG,"PWM Index %d, GPIO %d, on state %s, pwm %d%%", idx, gpio, onstate > 0 ? "On" : "Off", pwm);
|
||||
ESP_LOGD(TAG,"Index %d, GPIO %d, color/onstate %d / RMT %d, bright %d%%", idx, gpio, color, type, bright);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
void set_led_gpio(int gpio, char *value) {
|
||||
char *p;
|
||||
|
||||
if (strcasestr(value, "green")) {
|
||||
green.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) green.active = atoi(p + 1);
|
||||
} else if (strcasestr(value, "red")) {
|
||||
red.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) red.active = atoi(p + 1);
|
||||
}
|
||||
struct led_config_s *config;
|
||||
|
||||
if (strcasestr(value, "green")) config = &green;
|
||||
else config = &red;
|
||||
|
||||
config->gpio = gpio;
|
||||
char *p = value;
|
||||
while ((p = strchr(p, ':')) != NULL) {
|
||||
p++;
|
||||
if ((strcasestr(p, "ws2812")) != NULL) config->type = LED_WS2812;
|
||||
else config->color = atoi(p);
|
||||
}
|
||||
|
||||
if (config->type != LED_GPIO) {
|
||||
for (const struct rmt_led_param_s *p = rmt_led_param; p->type >= 0; p++) {
|
||||
if (p->type == config->type) {
|
||||
if (config == &green) config->color = p->green;
|
||||
else config->color = p->red;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void led_svc_init(void) {
|
||||
#ifdef CONFIG_LED_GREEN_GPIO_LEVEL
|
||||
green.active = CONFIG_LED_GREEN_GPIO_LEVEL;
|
||||
green.color = CONFIG_LED_GREEN_GPIO_LEVEL;
|
||||
#endif
|
||||
#ifdef CONFIG_LED_RED_GPIO_LEVEL
|
||||
red.active = CONFIG_LED_RED_GPIO_LEVEL;
|
||||
red.color = CONFIG_LED_RED_GPIO_LEVEL;
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_LED_LOCKED
|
||||
parse_set_GPIO(set_led_gpio);
|
||||
#endif
|
||||
|
||||
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
|
||||
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
|
||||
if (nvs_item) {
|
||||
PARSE_PARAM(nvs_item, "green", '=', green.pwm);
|
||||
PARSE_PARAM(nvs_item, "red", '=', red.pwm);
|
||||
PARSE_PARAM(nvs_item, "green", '=', green.bright);
|
||||
PARSE_PARAM(nvs_item, "red", '=', red.bright);
|
||||
free(nvs_item);
|
||||
}
|
||||
|
||||
led_config(LED_GREEN, green.gpio, green.active, green.pwm);
|
||||
led_config(LED_RED, red.gpio, red.active, red.pwm);
|
||||
|
||||
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, red.gpio, red.active, red.pwm );
|
||||
led_config(LED_GREEN, green.gpio, green.color, green.bright, green.type);
|
||||
led_config(LED_RED, red.gpio, red.color, red.bright, red.type);
|
||||
|
||||
ESP_LOGI(TAG,"Configuring LEDs green:%d (on:%d rmt:%d %d%% ), red:%d (on:%d rmt:%d %d%% )",
|
||||
green.gpio, green.color, green.type, green.bright,
|
||||
red.gpio, red.color, red.type, red.bright);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
@@ -8,20 +8,22 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef LED_H
|
||||
#define LED_H
|
||||
#include "driver/gpio.h"
|
||||
|
||||
enum { LED_GREEN = 0, LED_RED };
|
||||
typedef enum { LED_GPIO = -1, LED_WS2812 } led_type_t;
|
||||
|
||||
#define led_on(idx) led_blink_core(idx, 1, 0, false)
|
||||
#define led_off(idx) led_blink_core(idx, 0, 0, false)
|
||||
#define led_blink(idx, on, off) led_blink_core(idx, on, off, false)
|
||||
#define led_blink_pushed(idx, on, off) led_blink_core(idx, on, off, true)
|
||||
|
||||
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm);
|
||||
bool led_brightness(int idx, int percent);
|
||||
// if type is LED_GPIO then color set the GPIO logic value for "on"
|
||||
bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type);
|
||||
bool led_brightness(int idx, int percent);
|
||||
bool led_blink_core(int idx, int ontime, int offtime, bool push);
|
||||
bool led_unpush(int idx);
|
||||
int led_allocate(void);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task.h"
|
||||
#include "monitor.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "buttons.h"
|
||||
@@ -25,15 +26,14 @@
|
||||
#include "cJSON.h"
|
||||
#include "tools.h"
|
||||
|
||||
#define PSEUDO_IDLE_STACK_SIZE (6*1024)
|
||||
|
||||
#define MONITOR_TIMER (10*1000)
|
||||
#define SCRATCH_SIZE 256
|
||||
|
||||
static const char *TAG = "monitor";
|
||||
|
||||
static TimerHandle_t monitor_timer;
|
||||
|
||||
static monitor_gpio_t jack = { CONFIG_JACK_GPIO, 0 };
|
||||
static monitor_gpio_t spkfault = { CONFIG_SPKFAULT_GPIO, 0 };
|
||||
void (*pseudo_idle_svc)(uint32_t now);
|
||||
|
||||
void (*jack_handler_svc)(bool inserted);
|
||||
bool jack_inserted_svc(void);
|
||||
@@ -41,36 +41,39 @@ bool jack_inserted_svc(void);
|
||||
void (*spkfault_handler_svc)(bool inserted);
|
||||
bool spkfault_svc(void);
|
||||
|
||||
static monitor_gpio_t jack = { CONFIG_JACK_GPIO, 0 };
|
||||
static monitor_gpio_t spkfault = { CONFIG_SPKFAULT_GPIO, 0 };
|
||||
static bool monitor_stats;
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void task_stats( cJSON* top ) {
|
||||
#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
|
||||
#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
|
||||
#pragma message("Compiled with trace facility")
|
||||
static struct {
|
||||
TaskStatus_t *tasks;
|
||||
uint32_t total, n;
|
||||
} current, previous;
|
||||
|
||||
cJSON * tlist=cJSON_CreateArray();
|
||||
current.n = uxTaskGetNumberOfTasks();
|
||||
current.tasks = malloc_init_external( current.n * sizeof( TaskStatus_t ) );
|
||||
current.n = uxTaskGetSystemState( current.tasks, current.n, ¤t.total );
|
||||
cJSON_AddNumberToObject(top,"ntasks",current.n);
|
||||
|
||||
static EXT_RAM_ATTR char scratch[SCRATCH_SIZE];
|
||||
*scratch = '\0';
|
||||
|
||||
char scratch[SCRATCH_SIZE] = { };
|
||||
|
||||
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
||||
#pragma message("Compiled with runtime stats")
|
||||
uint32_t elapsed = current.total - previous.total;
|
||||
|
||||
|
||||
for(int i = 0, n = 0; i < current.n; i++ ) {
|
||||
for (int j = 0; j < previous.n; j++) {
|
||||
if (current.tasks[i].xTaskNumber == previous.tasks[j].xTaskNumber) {
|
||||
n += snprintf(scratch + n, SCRATCH_SIZE - n, "%16s (%u) %2u%% s:%5u", current.tasks[i].pcTaskName,
|
||||
n += snprintf(scratch + n, SCRATCH_SIZE - n, "%16s (%u) %2u%% s:%5u", current.tasks[i].pcTaskName,
|
||||
current.tasks[i].eCurrentState,
|
||||
100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed,
|
||||
100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed,
|
||||
current.tasks[i].usStackHighWaterMark);
|
||||
cJSON * t=cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(t,"cpu",100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed);
|
||||
@@ -84,11 +87,11 @@ static void task_stats( cJSON* top ) {
|
||||
if (i % 3 == 2 || i == current.n - 1) {
|
||||
ESP_LOGI(TAG, "%s", scratch);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
#pragma message("Compiled WITHOUT runtime stats")
|
||||
|
||||
@@ -105,21 +108,26 @@ static void task_stats( cJSON* top ) {
|
||||
if (i % 3 == 2 || i == current.n - 1) {
|
||||
ESP_LOGI(TAG, "%s", scratch);
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
cJSON_AddItemToObject(top,"tasks",tlist);
|
||||
if (previous.tasks) free(previous.tasks);
|
||||
previous = current;
|
||||
#else
|
||||
#pragma message("Compiled WITHOUT trace facility")
|
||||
#endif
|
||||
#else
|
||||
#pragma message("Compiled WITHOUT trace facility")
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void monitor_callback(TimerHandle_t xTimer) {
|
||||
static void monitor_trace(uint32_t now) {
|
||||
static uint32_t last;
|
||||
|
||||
if (now < last + MONITOR_TIMER) return;
|
||||
last = now;
|
||||
|
||||
cJSON * top=cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(top,"free_iram",heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
|
||||
cJSON_AddNumberToObject(top,"min_free_iram",heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
|
||||
@@ -133,7 +141,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
|
||||
|
||||
|
||||
task_stats(top);
|
||||
char * top_a= cJSON_PrintUnformatted(top);
|
||||
if(top_a){
|
||||
@@ -144,7 +152,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGI(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
|
||||
@@ -152,7 +160,7 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool jack_inserted_svc (void) {
|
||||
if (jack.gpio != -1) return button_is_pressed(jack.gpio, NULL);
|
||||
@@ -160,7 +168,7 @@ bool jack_inserted_svc (void) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void spkfault_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
|
||||
ESP_LOGD(TAG, "Speaker status %s", event == BUTTON_PRESSED ? "faulty" : "normal");
|
||||
@@ -170,22 +178,22 @@ static void spkfault_handler_default(void *id, button_event_e event, button_pres
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool spkfault_svc (void) {
|
||||
return button_is_pressed(spkfault.gpio, NULL);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CONFIG_JACK_LOCKED
|
||||
static void set_jack_gpio(int gpio, char *value) {
|
||||
if (strcasestr(value, "jack")) {
|
||||
char *p;
|
||||
jack.gpio = gpio;
|
||||
jack.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) jack.active = atoi(p + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
jack.gpio = -1;
|
||||
}
|
||||
@@ -193,15 +201,15 @@ static void set_jack_gpio(int gpio, char *value) {
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CONFIG_SPKFAULT_LOCKED
|
||||
static void set_spkfault_gpio(int gpio, char *value) {
|
||||
if (strcasestr(value, "spkfault")) {
|
||||
char *p;
|
||||
spkfault.gpio = gpio;
|
||||
spkfault.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) spkfault.active = atoi(p + 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spkfault.gpio = -1;
|
||||
}
|
||||
@@ -209,28 +217,41 @@ static void set_spkfault_gpio(int gpio, char *value) {
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
static void pseudo_idle(void *arg) {
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
uint32_t now = pdTICKS_TO_MS(xTaskGetTickCount());
|
||||
|
||||
if (monitor_stats) monitor_trace(now);
|
||||
if (pseudo_idle_svc) pseudo_idle_svc(now);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void monitor_svc_init(void) {
|
||||
ESP_LOGI(TAG, "Initializing monitoring");
|
||||
|
||||
|
||||
#ifdef CONFIG_JACK_GPIO_LEVEL
|
||||
jack.active = CONFIG_JACK_GPIO_LEVEL;
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_JACK_LOCKED
|
||||
parse_set_GPIO(set_jack_gpio);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// re-use button management for jack handler, it's a GPIO after all
|
||||
if (jack.gpio != -1) {
|
||||
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
|
||||
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
|
||||
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPKFAULT_GPIO_LEVEL
|
||||
spkfault.active = CONFIG_SPKFAULT_GPIO_LEVEL;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_SPKFAULT_LOCKED
|
||||
parse_set_GPIO(set_spkfault_gpio);
|
||||
@@ -238,18 +259,15 @@ void monitor_svc_init(void) {
|
||||
|
||||
// re-use button management for speaker fault handler, it's a GPIO after all
|
||||
if (spkfault.gpio != -1) {
|
||||
ESP_LOGI(TAG,"Adding speaker fault (%s) detection GPIO %d", spkfault.active ? "high" : "low", spkfault.gpio);
|
||||
ESP_LOGI(TAG,"Adding speaker fault (%s) detection GPIO %d", spkfault.active ? "high" : "low", spkfault.gpio);
|
||||
button_create(NULL, spkfault.gpio, spkfault.active ? BUTTON_HIGH : BUTTON_LOW, false, 0, spkfault_handler_default, 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// do we want stats
|
||||
char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
|
||||
if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
|
||||
monitor_timer = xTimerCreate("monitor", MONITOR_TIMER / portTICK_RATE_MS, pdTRUE, NULL, monitor_callback);
|
||||
xTimerStart(monitor_timer, portMAX_DELAY);
|
||||
}
|
||||
monitor_stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
FREE_AND_NULL(p);
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu) dma:%zu (min:%zu)",
|
||||
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
@@ -257,18 +275,24 @@ void monitor_svc_init(void) {
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
|
||||
|
||||
// pseudo-idle callback => don't use FreeRTOS idle callbacks so we can block (should not but ...)
|
||||
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
static EXT_RAM_ATTR StackType_t xStack[PSEUDO_IDLE_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
xTaskCreateStatic( (TaskFunction_t) pseudo_idle, "pseudo_idle", PSEUDO_IDLE_STACK_SIZE,
|
||||
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer );
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
monitor_gpio_t * get_spkfault_gpio(){
|
||||
return &spkfault ;
|
||||
}
|
||||
return &spkfault ;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
monitor_gpio_t * get_jack_insertion_gpio(){
|
||||
return &jack;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ typedef struct {
|
||||
int active;
|
||||
} monitor_gpio_t;
|
||||
|
||||
extern void (*pseudo_idle_svc)(uint32_t now);
|
||||
|
||||
extern void (*jack_handler_svc)(bool inserted);
|
||||
extern bool jack_inserted_svc(void);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/ledc.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "platform_config.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "battery.h"
|
||||
@@ -28,6 +29,7 @@ int i2c_system_port = I2C_SYSTEM_PORT;
|
||||
int i2c_system_speed = 400000;
|
||||
int spi_system_host = SPI_SYSTEM_HOST;
|
||||
int spi_system_dc_gpio = -1;
|
||||
int rmt_system_base_channel = RMT_CHANNEL_0;
|
||||
pwm_system_t pwm_system = {
|
||||
.timer = LEDC_TIMER_0,
|
||||
.base_channel = LEDC_CHANNEL_0,
|
||||
@@ -133,7 +135,11 @@ void services_init(void) {
|
||||
ledc_timer_config_t pwm_timer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
.freq_hz = 5000,
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||
#else
|
||||
.speed_mode = LEDC_HIGH_SPEED_MODE,
|
||||
#endif
|
||||
.timer_num = pwm_system.timer,
|
||||
};
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo
|
||||
|
||||
cJSON *item, *config = config_alloc_get_cjson("cspot_config");
|
||||
if ((item = cJSON_GetObjectItem(config, "volume")) != NULL) volume = item->valueint;
|
||||
if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint;
|
||||
if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint;
|
||||
if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring;
|
||||
else this->name = name;
|
||||
cJSON_Delete(config);
|
||||
@@ -306,8 +306,9 @@ void cspotPlayer::runTask() {
|
||||
// Register mdns service, for spotify to find us
|
||||
bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort,
|
||||
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
|
||||
|
||||
CSPOT_LOG(info, "CSpot instance service name %s (id %s)", blob->getDeviceName().c_str(), blob->getDeviceId().c_str());
|
||||
|
||||
static int count = 0;
|
||||
// gone with the wind...
|
||||
while (1) {
|
||||
clientConnected.wait();
|
||||
|
||||
@@ -163,15 +163,11 @@ static bool cmd_handler(cspot_event_t event, ...) {
|
||||
*/
|
||||
static void cspot_sink_start(nm_state_t state_id, int sub_state) {
|
||||
const char *hostname;
|
||||
uint8_t mac[6];
|
||||
|
||||
cmd_handler_chain = cspot_cbs.cmd;
|
||||
network_get_hostname(&hostname);
|
||||
|
||||
esp_netif_get_mac(network_get_active_interface(), mac);
|
||||
for (int i = 0; i < 6; i++) sprintf(deviceId + 2*i, "%02x", mac[i]);
|
||||
|
||||
ESP_LOGI(TAG, "Starting Spotify (CSpot) servicename %s with id %s", hostname, deviceId);
|
||||
ESP_LOGI(TAG, "starting Spotify on host %s", hostname);
|
||||
|
||||
int port;
|
||||
httpd_handle_t server = http_get_server(&port);
|
||||
|
||||
@@ -289,6 +289,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
raop_sync.enabled = !strcasestr(output.device, "BT");
|
||||
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
|
||||
break;
|
||||
case RAOP_STALLED:
|
||||
case RAOP_STOP:
|
||||
output.external = 0;
|
||||
__attribute__ ((fallthrough));
|
||||
@@ -492,7 +493,6 @@ void decode_restore(int external) {
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
case DECODE_RAOP:
|
||||
raop_disconnect();
|
||||
raop_state = RAOP_STOP;
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_CSPOT_SINK
|
||||
|
||||
@@ -15,13 +15,41 @@
|
||||
#include "esp_system.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include "monitor.h"
|
||||
#include "platform_config.h"
|
||||
#include "messaging.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "accessors.h"
|
||||
|
||||
#ifndef CONFIG_POWER_GPIO_LEVEL
|
||||
#define CONFIG_POWER_GPIO_LEVEL 1
|
||||
#endif
|
||||
|
||||
static const char TAG[] = "embedded";
|
||||
|
||||
static struct {
|
||||
int gpio, active;
|
||||
} power_control = { CONFIG_POWER_GPIO, CONFIG_POWER_GPIO_LEVEL };
|
||||
|
||||
extern void sb_controls_init(void);
|
||||
extern bool sb_displayer_init(void);
|
||||
|
||||
u8_t custom_player_id = 12;
|
||||
|
||||
mutex_type slimp_mutex;
|
||||
static jmp_buf jumpbuf;
|
||||
|
||||
#ifndef POWER_LOCKED
|
||||
static void set_power_gpio(int gpio, char *value) {
|
||||
if (strcasestr(value, "power")) {
|
||||
char *p = strchr(value, ':');
|
||||
if (p) power_control.active = atoi(p + 1);
|
||||
power_control.gpio = gpio;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void get_mac(u8_t mac[]) {
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
}
|
||||
@@ -56,16 +84,22 @@ uint32_t _gettime_ms_(void) {
|
||||
return (uint32_t) (esp_timer_get_time() / 1000);
|
||||
}
|
||||
|
||||
extern void sb_controls_init(void);
|
||||
extern bool sb_displayer_init(void);
|
||||
|
||||
u8_t custom_player_id = 12;
|
||||
|
||||
int embedded_init(void) {
|
||||
mutex_create(slimp_mutex);
|
||||
sb_controls_init();
|
||||
custom_player_id = sb_displayer_init() ? 100 : 101;
|
||||
|
||||
#ifndef POWER_LOCKED
|
||||
parse_set_GPIO(set_power_gpio);
|
||||
#endif
|
||||
|
||||
if (power_control.gpio != -1) {
|
||||
gpio_pad_select_gpio_x(power_control.gpio);
|
||||
gpio_set_direction_x(power_control.gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level_x(power_control.gpio, !power_control.active);
|
||||
ESP_LOGI(TAG, "setting power GPIO %d (active:%d)", power_control.gpio, power_control.active);
|
||||
}
|
||||
|
||||
return setjmp(jumpbuf);
|
||||
}
|
||||
|
||||
@@ -73,6 +107,13 @@ void embedded_exit(int code) {
|
||||
longjmp(jumpbuf, code + 1);
|
||||
}
|
||||
|
||||
void powering(bool on) {
|
||||
if (power_control.gpio != -1) {
|
||||
ESP_LOGI(TAG, "powering player %s", on ? "ON" : "OFF");
|
||||
gpio_set_level_x(power_control.gpio, on ? power_control.active : !power_control.active);
|
||||
}
|
||||
}
|
||||
|
||||
u16_t get_RSSI(void) {
|
||||
wifi_ap_record_t wifidata;
|
||||
esp_wifi_sta_get_ap_info(&wifidata);
|
||||
|
||||
@@ -71,6 +71,7 @@ int embedded_init(void);
|
||||
void register_external(void);
|
||||
void deregister_external(void);
|
||||
void decode_restore(int external);
|
||||
void powering(bool on);
|
||||
// used when other client wants to use slimproto socket to send messages
|
||||
extern mutex_type slimp_mutex;
|
||||
#define LOCK_P mutex_lock(slimp_mutex)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Philippe G. 2020, philippe_44@outlook.com
|
||||
@@ -8,71 +8,96 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "math.h"
|
||||
#include "platform_config.h"
|
||||
#include "squeezelite.h"
|
||||
#include "squeezelite.h"
|
||||
#include "equalizer.h"
|
||||
#include "esp_equalizer.h"
|
||||
|
||||
#define EQ_BANDS 10
|
||||
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
|
||||
static struct {
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
void *handle;
|
||||
float gain[EQ_BANDS];
|
||||
float loudness, volume;
|
||||
uint32_t samplerate;
|
||||
float gain[EQ_BANDS], loudness_gain[EQ_BANDS];
|
||||
bool update;
|
||||
} equalizer = { .update = true };
|
||||
} equalizer;
|
||||
|
||||
#define POLYNOME_COUNT 6
|
||||
|
||||
static const float loudness_envelope_coefficients[EQ_BANDS][POLYNOME_COUNT] = {
|
||||
{5.5169301499257067e+001, 6.3671410796029004e-001,
|
||||
-4.2663226432095233e-002, 8.1063072336581246e-004,
|
||||
-7.3621858933917722e-006, 2.5349489594339575e-008},
|
||||
{3.7716143859944118e+001, 1.2355293276538579e+000,
|
||||
-6.6435374582217863e-002, 1.2976763440259382e-003,
|
||||
-1.1978732496353172e-005, 4.1664114634622593e-008},
|
||||
{2.5103632377146837e+001, 1.3259150615414637e+000,
|
||||
-6.6332442135695099e-002, 1.2845279812261677e-003,
|
||||
-1.1799885217545631e-005, 4.0925911584040685e-008},
|
||||
{1.3159168212144563e+001, 8.8149357628440639e-001,
|
||||
-4.0384121097225931e-002, 7.3843501027501322e-004,
|
||||
-6.5508794453097008e-006, 2.2221997141120518e-008},
|
||||
{5.1337853800151700e+000, 4.0817077967582394e-001,
|
||||
-1.4107826528626457e-002, 1.5251066311713760e-004,
|
||||
-3.6689819583740298e-007, -2.0390798774727989e-009},
|
||||
{3.1432364156464315e-001, 9.1260548140023004e-002,
|
||||
-3.5012124633183438e-004, -8.6023911664606992e-005,
|
||||
1.6785606828245921e-006, -8.8269731094371646e-009},
|
||||
{-4.0965062397075833e+000, 1.3667010948271402e-001,
|
||||
2.4775896786988390e-004, -9.6620399661858641e-005,
|
||||
1.7733690952379155e-006, -9.1583104942496635e-009},
|
||||
{-9.0275786029994176e+000, 2.6226938845184250e-001,
|
||||
-6.5777547972402156e-003, 1.0045957188977551e-004,
|
||||
-7.8851000325128971e-007, 2.4639885209682384e-009},
|
||||
{-4.4275018199195815e+000, 4.5399572638241725e-001,
|
||||
-2.4034902766833462e-002, 5.9828953622534668e-004,
|
||||
-6.2893971217140864e-006, 2.3133296592719627e-008},
|
||||
{1.4243299202697818e+001, 3.6984458807056630e-001,
|
||||
-3.0413994109395680e-002, 7.6700105080386904e-004,
|
||||
-8.2777185209388079e-006, 3.1352890650784970e-008} };
|
||||
|
||||
/****************************************************************************************
|
||||
* calculate loudness gains
|
||||
*/
|
||||
static void calculate_loudness(void) {
|
||||
char trace[EQ_BANDS * 5 + 1];
|
||||
size_t n = 0;
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
for (int j = 0; j < POLYNOME_COUNT && equalizer.loudness != 0; j++) {
|
||||
equalizer.loudness_gain[i] +=
|
||||
loudness_envelope_coefficients[i][j] * pow(equalizer.volume, j);
|
||||
}
|
||||
equalizer.loudness_gain[i] *= equalizer.loudness / 2;
|
||||
n += sprintf(trace + n, "%.2g%c", equalizer.loudness_gain[i], i < EQ_BANDS ? ',' : '\0');
|
||||
}
|
||||
LOG_INFO("loudness %s", trace);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* initialize equalizer
|
||||
*/
|
||||
void equalizer_init(void) {
|
||||
s8_t gain[EQ_BANDS] = { };
|
||||
// handle equalizer
|
||||
char *config = config_alloc_get(NVS_TYPE_STR, "equalizer");
|
||||
char *p = strtok(config, ", !");
|
||||
|
||||
char *p = strtok(config, ", !");
|
||||
|
||||
for (int i = 0; p && i < EQ_BANDS; i++) {
|
||||
gain[i] = atoi(p);
|
||||
equalizer.gain[i] = atoi(p);
|
||||
p = strtok(NULL, ", :");
|
||||
}
|
||||
|
||||
free(config);
|
||||
equalizer_update(gain);
|
||||
|
||||
LOG_INFO("initializing equalizer");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* open equalizer
|
||||
*/
|
||||
void equalizer_open(u32_t sample_rate) {
|
||||
// in any case, need to clear update flag
|
||||
equalizer.update = false;
|
||||
|
||||
if (sample_rate != 11025 && sample_rate != 22050 && sample_rate != 44100 && sample_rate != 48000) {
|
||||
LOG_WARN("equalizer only supports 11025, 22050, 44100 and 48000 sample rates, not %u", sample_rate);
|
||||
return;
|
||||
}
|
||||
|
||||
equalizer.handle = esp_equalizer_init(2, sample_rate, EQ_BANDS, 0);
|
||||
|
||||
if (equalizer.handle) {
|
||||
bool active = false;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
esp_equalizer_set_band_value(equalizer.handle, equalizer.gain[i], i, 0);
|
||||
esp_equalizer_set_band_value(equalizer.handle, equalizer.gain[i], i, 1);
|
||||
active |= equalizer.gain[i] != 0;
|
||||
}
|
||||
|
||||
// do not activate equalizer if all gain are 0
|
||||
if (!active) equalizer_close();
|
||||
|
||||
LOG_INFO("equalizer initialized %u", active);
|
||||
} else {
|
||||
LOG_WARN("can't init equalizer");
|
||||
}
|
||||
}
|
||||
|
||||
free(config);
|
||||
|
||||
// handle loudness
|
||||
config = config_alloc_get(NVS_TYPE_STR, "loudness");
|
||||
equalizer.loudness = atof(config) / 10.0;
|
||||
|
||||
free(config);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* close equalizer
|
||||
@@ -82,36 +107,123 @@ void equalizer_close(void) {
|
||||
esp_equalizer_uninit(equalizer.handle);
|
||||
equalizer.handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* update equalizer gain
|
||||
*/
|
||||
void equalizer_update(s8_t *gain) {
|
||||
char config[EQ_BANDS * 4 + 1] = { };
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
equalizer.gain[i] = gain[i];
|
||||
n += sprintf(config + n, "%d,", gain[i]);
|
||||
}
|
||||
|
||||
config[n-1] = '\0';
|
||||
config_set_value(NVS_TYPE_STR, "equalizer", config);
|
||||
equalizer.update = true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* process equalizer
|
||||
* change sample rate
|
||||
*/
|
||||
void equalizer_process(u8_t *buf, u32_t bytes, u32_t sample_rate) {
|
||||
void equalizer_set_samplerate(uint32_t samplerate) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
if (equalizer.samplerate != samplerate) equalizer_close();
|
||||
equalizer.samplerate = samplerate;
|
||||
equalizer.update = true;
|
||||
|
||||
LOG_INFO("equalizer sample rate %u", samplerate);
|
||||
#else
|
||||
LOG_INFO("no equalizer with 32 bits samples");
|
||||
#endif
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* get volume update and recalculate loudness according to
|
||||
*/
|
||||
void equalizer_set_volume(unsigned left, unsigned right) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
float volume = (left + right) / 2;
|
||||
// do classic dB conversion and scale it 0..100
|
||||
if (volume) volume = log2(volume);
|
||||
volume = volume / 16.0 * 100.0;
|
||||
|
||||
// LMS has the bad habit to send multiple volume commands
|
||||
if (volume != equalizer.volume && equalizer.loudness) {
|
||||
equalizer.volume = volume;
|
||||
calculate_loudness();
|
||||
equalizer.update = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change gains from LMS
|
||||
*/
|
||||
void equalizer_set_gain(int8_t *gain) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
char config[EQ_BANDS * 4 + 1] = { };
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
equalizer.gain[i] = gain[i];
|
||||
n += sprintf(config + n, "%d,", gain[i]);
|
||||
}
|
||||
|
||||
config[n-1] = '\0';
|
||||
config_set_value(NVS_TYPE_STR, "equalizer", config);
|
||||
|
||||
// update only if something changed
|
||||
if (!memcmp(equalizer.gain, gain, EQ_BANDS)) equalizer.update = true;
|
||||
|
||||
LOG_INFO("equalizer gain %s", config);
|
||||
#else
|
||||
LOG_INFO("no equalizer with 32 bits samples");
|
||||
#endif
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change loudness from LMS
|
||||
*/
|
||||
void equalizer_set_loudness(uint8_t loudness) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
char p[4];
|
||||
itoa(loudness, p, 10);
|
||||
config_set_value(NVS_TYPE_STR, "loudness", p);
|
||||
|
||||
// update loudness gains as a factor of loudness and volume
|
||||
if (equalizer.loudness != loudness / 10.0) {
|
||||
equalizer.loudness = loudness / 10.0;
|
||||
calculate_loudness();
|
||||
equalizer.update = true;
|
||||
}
|
||||
|
||||
LOG_INFO("loudness %u", (unsigned) loudness);
|
||||
#else
|
||||
LOG_INFO("no equalizer with 32 bits samples");
|
||||
#endif
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* process equalizer
|
||||
*/
|
||||
void equalizer_process(uint8_t *buf, uint32_t bytes) {
|
||||
#if BYTES_PER_FRAME == 4
|
||||
// don't want to process with output locked, so take the small risk to miss one parametric update
|
||||
if (equalizer.update) {
|
||||
equalizer_close();
|
||||
equalizer_open(sample_rate);
|
||||
equalizer.update = false;
|
||||
|
||||
if (equalizer.samplerate != 11025 && equalizer.samplerate != 22050 && equalizer.samplerate != 44100 && equalizer.samplerate != 48000) {
|
||||
LOG_WARN("equalizer only supports 11025, 22050, 44100 and 48000 sample rates, not %u", equalizer.samplerate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!equalizer.handle && ((equalizer.handle = esp_equalizer_init(2, equalizer.samplerate, EQ_BANDS, 0)) == NULL)) {
|
||||
LOG_WARN("can't init equalizer");
|
||||
return;
|
||||
}
|
||||
|
||||
bool active = false;
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
float gain = equalizer.gain[i] + equalizer.loudness_gain[i];
|
||||
esp_equalizer_set_band_value(equalizer.handle, gain, i, 0);
|
||||
esp_equalizer_set_band_value(equalizer.handle, gain, i, 1);
|
||||
active |= gain != 0;
|
||||
}
|
||||
|
||||
// at the end do not activate equalizer if all gain are 0
|
||||
if (!active) equalizer_close();
|
||||
LOG_INFO("equalizer %s", active ? "actived" : "deactivated");
|
||||
}
|
||||
|
||||
|
||||
if (equalizer.handle) {
|
||||
esp_equalizer_process(equalizer.handle, buf, bytes, sample_rate, 2);
|
||||
}
|
||||
esp_equalizer_process(equalizer.handle, buf, bytes, equalizer.samplerate, 2);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -11,7 +11,9 @@
|
||||
#pragma once
|
||||
|
||||
void equalizer_init(void);
|
||||
void equalizer_open(u32_t sample_rate);
|
||||
void equalizer_close(void);
|
||||
void equalizer_update(s8_t *gain);
|
||||
void equalizer_process(u8_t *buf, u32_t bytes, u32_t sample_rate);
|
||||
void equalizer_set_samplerate(uint32_t samplerate);
|
||||
void equalizer_set_gain(int8_t *gain);
|
||||
void equalizer_set_loudness(u8_t loudness);
|
||||
void equalizer_set_volume(unsigned left, unsigned right);
|
||||
void equalizer_process(uint8_t *buf, uint32_t bytes);
|
||||
|
||||
@@ -69,6 +69,7 @@ void output_init_bt(log_level level, char *device, unsigned output_buf_size, cha
|
||||
char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
|
||||
stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
free(p);
|
||||
equalizer_set_samplerate(output.current_sample_rate);
|
||||
}
|
||||
|
||||
void output_close_bt(void) {
|
||||
@@ -143,7 +144,7 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
|
||||
output.frames_in_process = oframes;
|
||||
UNLOCK;
|
||||
|
||||
equalizer_process(data, oframes * BYTES_PER_FRAME, output.current_sample_rate);
|
||||
equalizer_process(data, oframes * BYTES_PER_FRAME);
|
||||
|
||||
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
|
||||
SET_MIN_MAX((len-oframes*BYTES_PER_FRAME), rec);
|
||||
|
||||
@@ -44,6 +44,11 @@ static void (*close_cb)(void);
|
||||
struct eqlz_packet {
|
||||
char opcode[4];
|
||||
};
|
||||
|
||||
struct loud_packet {
|
||||
char opcode[4];
|
||||
u8_t loudness;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static bool handler(u8_t *data, int len){
|
||||
@@ -51,9 +56,12 @@ static bool handler(u8_t *data, int len){
|
||||
|
||||
if (!strncmp((char*) data, "eqlz", 4)) {
|
||||
s8_t *gain = (s8_t*) (data + sizeof(struct eqlz_packet));
|
||||
LOG_INFO("got equalizer %d", len);
|
||||
// update will be done at next opportunity
|
||||
equalizer_update(gain);
|
||||
equalizer_set_gain(gain);
|
||||
} else if (!strncmp((char*) data, "loud", 4)) {
|
||||
struct loud_packet *packet = (struct loud_packet*) data;
|
||||
// update will be done at next opportunity
|
||||
equalizer_set_loudness(packet->loudness);
|
||||
} else {
|
||||
res = false;
|
||||
}
|
||||
@@ -114,7 +122,8 @@ void set_volume(unsigned left, unsigned right) {
|
||||
output.gainL = left;
|
||||
output.gainR = right;
|
||||
UNLOCK;
|
||||
}
|
||||
}
|
||||
equalizer_set_volume(left, right);
|
||||
}
|
||||
|
||||
bool test_open(const char *device, unsigned rates[], bool userdef_rates) {
|
||||
|
||||
@@ -54,10 +54,23 @@ sure that using rate_delay would fix that
|
||||
#define FRAME_BLOCK MAX_SILENCE_FRAMES
|
||||
#define SPDIF_BLOCK 256
|
||||
|
||||
// must have an integer ratio with FRAME_BLOCK (see spdif comment)
|
||||
#define DMA_BUF_LEN 512
|
||||
/* we produce FRAME_BLOCK (2048) per loop of the i2s thread so it's better if they fit
|
||||
* inside a set of DMA buffer nicely, i.e. DMA_BUF_FRAMES * DMA_BUF_COUNT is a multiple
|
||||
* of FRAME_BLOCK so that each DMA buffer is filled and we fully empty a FRAME_BLOCK at
|
||||
* each loop. Because one DMA buffer in esp32 is 4092 or below, when using 16 bits
|
||||
* samples and 2 channels, the best multiple is 512 (512*2*2=2048) and we use 6 of these.
|
||||
* In SPDIF, as we virtually use 32 bits per sample, the next proper multiple would
|
||||
* be 256 but such DMA buffers are too small and this causes stuttering. So we will use
|
||||
* non-multiples which means that at every loop one DMA buffer will be not fully filled.
|
||||
* At least, let's make sure it's not a too small amount of samples so 450*4*2=3600 fits
|
||||
* nicely in one DMA buffer and 2048/450 = 4 buffers + ~1/2 buffer which is acceptable.
|
||||
*/
|
||||
#define DMA_BUF_FRAMES 512
|
||||
#define DMA_BUF_COUNT 12
|
||||
|
||||
#define DMA_BUF_FRAMES_SPDIF 450
|
||||
#define DMA_BUF_COUNT_SPDIF 7
|
||||
|
||||
#define DECLARE_ALL_MIN_MAX \
|
||||
DECLARE_MIN_MAX(o); \
|
||||
DECLARE_MIN_MAX(s); \
|
||||
@@ -73,7 +86,7 @@ sure that using rate_delay would fix that
|
||||
RESET_MIN_MAX(buffering);
|
||||
|
||||
#define STATS_PERIOD_MS 5000
|
||||
#define STAT_STACK_SIZE (3*1024)
|
||||
static void (*pseudo_idle_chain)(uint32_t now);
|
||||
|
||||
#ifndef CONFIG_AMP_GPIO_LEVEL
|
||||
#define CONFIG_AMP_GPIO_LEVEL 1
|
||||
@@ -101,8 +114,7 @@ static struct {
|
||||
size_t count;
|
||||
} spdif;
|
||||
static size_t dma_buf_frames;
|
||||
static TaskHandle_t stats_task, output_i2s_task;
|
||||
static bool stats;
|
||||
static TaskHandle_t output_i2s_task;
|
||||
static struct {
|
||||
int gpio, active;
|
||||
} amp_control = { CONFIG_AMP_GPIO, CONFIG_AMP_GPIO_LEVEL },
|
||||
@@ -113,7 +125,7 @@ DECLARE_ALL_MIN_MAX;
|
||||
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, u8_t flags,
|
||||
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
|
||||
static void output_thread_i2s(void *arg);
|
||||
static void output_thread_i2s_stats(void *arg);
|
||||
static void i2s_stats(uint32_t now);
|
||||
static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count);
|
||||
static void (*jack_handler_chain)(bool inserted);
|
||||
|
||||
@@ -251,8 +263,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
return;
|
||||
}
|
||||
|
||||
/* BEWARE: i2s.c must be patched otherwise L/R are swapped in 32 bits mode */
|
||||
|
||||
// common I2S initialization
|
||||
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
@@ -263,6 +273,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
i2s_config.use_apll = true;
|
||||
#endif
|
||||
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
||||
i2s_config.dma_buf_len = DMA_BUF_FRAMES;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
||||
|
||||
if (strcasestr(device, "spdif")) {
|
||||
spdif.enabled = true;
|
||||
@@ -279,14 +291,14 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
i2s_config.sample_rate = output.current_sample_rate * 2;
|
||||
i2s_config.bits_per_sample = 32;
|
||||
// Normally counted in frames, but 16 sample are transformed into 32 bits in spdif
|
||||
i2s_config.dma_buf_len = DMA_BUF_LEN / 2;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT * 2;
|
||||
i2s_config.dma_buf_len = DMA_BUF_FRAMES_SPDIF;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT_SPDIF;
|
||||
/*
|
||||
In DMA, we have room for (LEN * COUNT) frames of 32 bits samples that
|
||||
we push at sample_rate * 2. Each of these pseudo-frames is a single true
|
||||
audio frame. So the real depth in true frames is (LEN * COUNT / 2)
|
||||
*/
|
||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;
|
||||
dma_buf_frames = i2s_config.dma_buf_len * i2s_config.dma_buf_count / 2;
|
||||
|
||||
// silence DAC output if sharing the same ws/bck
|
||||
if (i2s_dac_pin.ws_io_num == i2s_spdif_pin.ws_io_num && i2s_dac_pin.bck_io_num == i2s_spdif_pin.bck_io_num) silent_do = i2s_dac_pin.data_out_num;
|
||||
@@ -298,9 +310,9 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
i2s_config.sample_rate = output.current_sample_rate;
|
||||
i2s_config.bits_per_sample = BYTES_PER_FRAME * 8 / 2;
|
||||
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
|
||||
i2s_config.dma_buf_len = DMA_BUF_LEN;
|
||||
i2s_config.dma_buf_len = DMA_BUF_FRAMES;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN;
|
||||
dma_buf_frames = i2s_config.dma_buf_len * i2s_config.dma_buf_count;
|
||||
|
||||
// silence SPDIF output
|
||||
silent_do = i2s_spdif_pin.data_out_num;
|
||||
@@ -376,6 +388,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
i2s_stop(CONFIG_I2S_NUM);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
isI2SStarted=false;
|
||||
|
||||
equalizer_set_samplerate(output.current_sample_rate);
|
||||
|
||||
adac->power(ADAC_STANDBY);
|
||||
|
||||
@@ -408,17 +422,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
|
||||
// do we want stats
|
||||
p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
|
||||
stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
free(p);
|
||||
|
||||
// memory still used but at least task is not created
|
||||
if (stats) {
|
||||
// we allocate TCB but stack is static to avoid SPIRAM fragmentation
|
||||
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE,
|
||||
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
|
||||
}
|
||||
if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
|
||||
pseudo_idle_chain = pseudo_idle_svc;
|
||||
pseudo_idle_svc = i2s_stats;
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
|
||||
|
||||
@@ -431,7 +439,6 @@ void output_close_i2s(void) {
|
||||
UNLOCK;
|
||||
|
||||
while (!ended) vTaskDelay(20 / portTICK_PERIOD_MS);
|
||||
if (stats) vTaskDelete(stats_task);
|
||||
|
||||
i2s_driver_uninstall(CONFIG_I2S_NUM);
|
||||
free(obuf);
|
||||
@@ -486,7 +493,7 @@ static void output_thread_i2s(void *arg) {
|
||||
uint32_t fullness = gettime_ms();
|
||||
bool synced;
|
||||
output_state state = OUTPUT_OFF - 1;
|
||||
|
||||
|
||||
while (running) {
|
||||
|
||||
TIME_MEASUREMENT_START(timer_start);
|
||||
@@ -532,10 +539,11 @@ static void output_thread_i2s(void *arg) {
|
||||
output.frames_played_dmp = output.frames_played;
|
||||
// try to estimate how much we have consumed from the DMA buffer (calculation is incorrect at the very beginning ...)
|
||||
output.device_frames = dma_buf_frames - ((output.updated - fullness) * output.current_sample_rate) / 1000;
|
||||
// we'll try to produce iframes if we have any, but we might return less if outpuf does not have enough
|
||||
_output_frames( iframes );
|
||||
// oframes must be a global updated by the write callback
|
||||
output.frames_in_process = oframes;
|
||||
|
||||
|
||||
SET_MIN_MAX_SIZED(oframes,rec,iframes);
|
||||
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
|
||||
SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
|
||||
@@ -548,12 +556,12 @@ static void output_thread_i2s(void *arg) {
|
||||
discard = output.frames_played_dmp ? 0 : output.device_frames;
|
||||
synced = true;
|
||||
} else if (discard) {
|
||||
discard -= oframes;
|
||||
iframes = discard ? min(FRAME_BLOCK, discard) : FRAME_BLOCK;
|
||||
discard -= min(oframes, discard);
|
||||
iframes = discard ? min(FRAME_BLOCK, discard) : FRAME_BLOCK;
|
||||
UNLOCK;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
UNLOCK;
|
||||
|
||||
// now send all the data
|
||||
@@ -566,7 +574,7 @@ static void output_thread_i2s(void *arg) {
|
||||
i2s_start(CONFIG_I2S_NUM);
|
||||
adac->power(ADAC_ON);
|
||||
}
|
||||
|
||||
|
||||
// this does not work well as set_sample_rates resets the fifos (and it's too early)
|
||||
if (i2s_config.sample_rate != output.current_sample_rate) {
|
||||
LOG_INFO("changing sampling rate %u to %u", i2s_config.sample_rate, output.current_sample_rate);
|
||||
@@ -581,16 +589,11 @@ static void output_thread_i2s(void *arg) {
|
||||
i2s_set_sample_rates(CONFIG_I2S_NUM, spdif.enabled ? i2s_config.sample_rate * 2 : i2s_config.sample_rate);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
|
||||
#if BYTES_PER_FRAME == 4
|
||||
equalizer_close();
|
||||
equalizer_open(output.current_sample_rate);
|
||||
#endif
|
||||
equalizer_set_samplerate(output.current_sample_rate);
|
||||
}
|
||||
|
||||
#if BYTES_PER_FRAME == 4
|
||||
// run equalizer
|
||||
equalizer_process(obuf, oframes * BYTES_PER_FRAME, output.current_sample_rate);
|
||||
#endif
|
||||
equalizer_process(obuf, oframes * BYTES_PER_FRAME);
|
||||
|
||||
// we assume that here we have been able to entirely fill the DMA buffers
|
||||
if (spdif.enabled) {
|
||||
@@ -599,11 +602,11 @@ static void output_thread_i2s(void *arg) {
|
||||
// need IRAM for speed but can't allocate a FRAME_BLOCK * 16, so process by smaller chunks
|
||||
while (count < oframes) {
|
||||
size_t chunk = min(SPDIF_BLOCK, oframes - count);
|
||||
spdif_convert((ISAMPLE_T*) obuf + count * 2, chunk, (u32_t*) spdif.buf, &spdif.count);
|
||||
spdif_convert((ISAMPLE_T*) obuf + count * 2, chunk, (u32_t*) spdif.buf, &spdif.count);
|
||||
i2s_write(CONFIG_I2S_NUM, spdif.buf, chunk * 16, &obytes, portMAX_DELAY);
|
||||
bytes += obytes / (16 / BYTES_PER_FRAME);
|
||||
count += chunk;
|
||||
}
|
||||
}
|
||||
#if BYTES_PER_FRAME == 4
|
||||
} else if (i2s_config.bits_per_sample == 32) {
|
||||
i2s_write_expand(CONFIG_I2S_NUM, obuf, oframes * BYTES_PER_FRAME, 16, 32, &bytes, portMAX_DELAY);
|
||||
@@ -631,33 +634,34 @@ static void output_thread_i2s(void *arg) {
|
||||
/****************************************************************************************
|
||||
* Stats output thread
|
||||
*/
|
||||
static void output_thread_i2s_stats(void *arg) {
|
||||
while (1) {
|
||||
// no need to lock
|
||||
output_state state = output.state;
|
||||
|
||||
if(stats && state>OUTPUT_STOPPED){
|
||||
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, BYTES_PER_FRAME);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD3);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD4);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_STREAM, LINE_MIN_MAX_STREAM("stream",s));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output",o));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
|
||||
LOG_INFO("");
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+ ");
|
||||
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+ ");
|
||||
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Buffering(us)",buffering));
|
||||
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("i2s tfr(us)",i2s_time));
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+");
|
||||
RESET_ALL_MIN_MAX;
|
||||
}
|
||||
vTaskDelay( pdMS_TO_TICKS( STATS_PERIOD_MS ) );
|
||||
}
|
||||
static void i2s_stats(uint32_t now) {
|
||||
static uint32_t last;
|
||||
|
||||
// first chain to next handler
|
||||
if (pseudo_idle_chain) pseudo_idle_chain(now);
|
||||
|
||||
// then see if we need to act
|
||||
if (output.state <= OUTPUT_STOPPED || now < last + STATS_PERIOD_MS) return;
|
||||
last = now;
|
||||
|
||||
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d", output.state, output.current_sample_rate, BYTES_PER_FRAME);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD3);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD4);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_STREAM, LINE_MIN_MAX_STREAM("stream",s));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("output",o));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT,LINE_MIN_MAX("received",rec));
|
||||
LOG_INFO(LINE_MIN_MAX_FORMAT_FOOTER);
|
||||
LOG_INFO("");
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+ ");
|
||||
LOG_INFO(" max (us) | min (us) | avg(us) | count | ");
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+ ");
|
||||
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("Buffering(us)",buffering));
|
||||
LOG_INFO(LINE_MIN_MAX_DURATION_FORMAT,LINE_MIN_MAX_DURATION("i2s tfr(us)",i2s_time));
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+");
|
||||
RESET_ALL_MIN_MAX;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -708,12 +712,16 @@ static const u16_t spdif_bmclookup[256] = { //biphase mark encoded values (least
|
||||
|
||||
/*
|
||||
SPDIF is supposed to be (before BMC encoding, from LSB to MSB)
|
||||
PPPP AAAA SSSS SSSS SSSS SSSS SSSS VUCP
|
||||
after BMC encoding, each bits becomes 2 hence this becomes a 64 bits word. The
|
||||
the trick is to start not with a PPPP sequence but with an VUCP sequence to that
|
||||
the 16 bits samples are aligned with a BMC word boundary. Note that the LSB of the
|
||||
audio is transmitted first (not the MSB) and that ESP32 libray sends R then L,
|
||||
contrary to what seems to be usually done, so (dst) order had to be changed
|
||||
0.... 1... 191.. 0
|
||||
BLFMRF MLFWRF MLFWRF BLFMRF (B,M,W=preamble-4, L/R=left/Right-24, F=Flags-4)
|
||||
each xLF pattern is 32 bits
|
||||
PPPP AAAA SSSS SSSS SSSS SSSS SSSS VUCP (P=preamble, A=auxiliary, S=sample-20bits, V=valid, U=user data, C=channel status, P=parity)
|
||||
After BMC encoding, each bit becomes 2 hence this becomes a 64 bits word. The parity
|
||||
is fixed by changing AAAA bits so that VUPC does not change. Then then trick is to
|
||||
start not with a PPPP sequence but with an VUCP sequence to that the 16 bits samples
|
||||
are aligned with a BMC word boundary. Input buffer is left first => LRLR...
|
||||
The I2S interface must output first the B/M/W preamble which means that second
|
||||
32 bits words must be first and so must be marked right channel.
|
||||
*/
|
||||
static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count) {
|
||||
register u16_t hi, lo, aux;
|
||||
@@ -728,7 +736,6 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, s
|
||||
// invert if last preceeding bit is 1
|
||||
lo ^= ~((s16_t)hi) >> 16;
|
||||
// first 16 bits
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
aux = 0xb333 ^ (((u32_t)((s16_t)lo)) >> 17);
|
||||
#else
|
||||
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
|
||||
@@ -737,39 +744,38 @@ static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, s
|
||||
// invert if last preceeding bit is 1
|
||||
lo ^= ~((s16_t)hi) >> 16;
|
||||
// first 16 bits
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
// we use 20 bits samples as we need to force parity
|
||||
aux = spdif_bmclookup[(u8_t)(*src++ >> 12)];
|
||||
aux = (u8_t) (aux ^ (~((s16_t)lo) >> 16));
|
||||
aux |= (0xb3 ^ (((u16_t)((s8_t)aux)) >> 9)) << 8;
|
||||
#endif
|
||||
|
||||
// VUCP-Bits: Valid, Subcode, Channelstatus, Parity = 0
|
||||
// As parity is always 0, we can use fixed preambles
|
||||
// set special preamble every 192 iteration
|
||||
if (++cnt > 191) {
|
||||
*dst++ = VUCP | (PREAMBLE_B << 16 ) | aux; //special preamble for one of 192 frames
|
||||
cnt = 0;
|
||||
} else {
|
||||
*dst++ = VUCP | (PREAMBLE_M << 16) | aux;
|
||||
}
|
||||
}
|
||||
// now write sample's 16 low bits
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
|
||||
// then do right channel, no need to check PREAMBLE_B
|
||||
#if BYTES_PER_FRAME == 4
|
||||
hi = spdif_bmclookup[(u8_t)(*src >> 8)];
|
||||
lo = spdif_bmclookup[(u8_t) *src++];
|
||||
lo ^= ~((s16_t)hi) >> 16;
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
aux = 0xb333 ^ (((u32_t)((s16_t)lo)) >> 17);
|
||||
#else
|
||||
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
|
||||
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
|
||||
lo ^= ~((s16_t)hi) >> 16;
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
aux = spdif_bmclookup[(u8_t)(*src++ >> 12)];
|
||||
aux = (u8_t) (aux ^ (~((s16_t)lo) >> 16));
|
||||
aux |= (0xb3 ^ (((u16_t)((s8_t)aux)) >> 9)) << 8;
|
||||
#endif
|
||||
*dst++ = VUCP | (PREAMBLE_W << 16) | aux;
|
||||
*dst++ = ((u32_t)lo << 16) | hi;
|
||||
}
|
||||
|
||||
*count = cnt;
|
||||
|
||||
@@ -441,6 +441,10 @@ static void process_aude(u8_t *pkt, int len) {
|
||||
struct aude_packet *aude = (struct aude_packet *)pkt;
|
||||
|
||||
LOG_DEBUG("enable spdif: %d dac: %d", aude->enable_spdif, aude->enable_dac);
|
||||
|
||||
#if EMBEDDED
|
||||
powering(aude->enable_spdif),
|
||||
#endif
|
||||
|
||||
LOCK_O;
|
||||
if (!aude->enable_spdif && output.state != OUTPUT_OFF) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include "driver/rmt.h"
|
||||
#include "globdefs.h"
|
||||
#include "monitor.h"
|
||||
#include "targets.h"
|
||||
|
||||
@@ -15,7 +16,6 @@
|
||||
//*********************** NeoPixels ***************************
|
||||
////////////////////////////////////////////////////////////////
|
||||
#define NUM_LEDS 1
|
||||
#define LED_RMT_TX_CHANNEL 0
|
||||
#define LED_RMT_TX_GPIO 22
|
||||
|
||||
#define BITS_PER_LED_CMD 24
|
||||
@@ -39,6 +39,8 @@ struct led_state {
|
||||
uint32_t leds[NUM_LEDS];
|
||||
};
|
||||
|
||||
static int rmt_channel;
|
||||
|
||||
void ws2812_control_init(void);
|
||||
void ws2812_write_leds(struct led_state new_state);
|
||||
|
||||
@@ -93,9 +95,10 @@ void setup_rmt_data_buffer(struct led_state new_state);
|
||||
|
||||
void ws2812_control_init(void)
|
||||
{
|
||||
rmt_channel = rmt_system_base_channel++;
|
||||
rmt_config_t config;
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.channel = LED_RMT_TX_CHANNEL;
|
||||
config.channel = rmt_channel;
|
||||
config.gpio_num = LED_RMT_TX_GPIO;
|
||||
config.mem_block_num = 3;
|
||||
config.tx_config.loop_en = false;
|
||||
@@ -106,11 +109,13 @@ void ws2812_control_init(void)
|
||||
|
||||
ESP_ERROR_CHECK(rmt_config(&config));
|
||||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
|
||||
|
||||
ESP_LOGI(TAG, "LED wth ws2812 using gpio %d and channel %d", LED_RMT_TX_GPIO, rmt_channel);
|
||||
}
|
||||
|
||||
void ws2812_write_leds(struct led_state new_state) {
|
||||
setup_rmt_data_buffer(new_state);
|
||||
rmt_write_items(LED_RMT_TX_CHANNEL, led_data_buffer, LED_BUFFER_ITEMS, false);
|
||||
rmt_write_items(rmt_channel, led_data_buffer, LED_BUFFER_ITEMS, false);
|
||||
}
|
||||
|
||||
void setup_rmt_data_buffer(struct led_state new_state)
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
components/wifi-manager/webapp/dist/index.html.gz
vendored
BIN
components/wifi-manager/webapp/dist/index.html.gz
vendored
Binary file not shown.
2
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js
vendored
Normal file
2
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js.gz
vendored
Normal file
BIN
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js.gz
vendored
Normal file
Binary file not shown.
1
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js.map
vendored
Normal file
1
components/wifi-manager/webapp/dist/js/index.35c7f4.bundle.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
1
components/wifi-manager/webapp/dist/js/node_vendors.35c7f4.bundle.js.map
vendored
Normal file
1
components/wifi-manager/webapp/dist/js/node_vendors.35c7f4.bundle.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -59,6 +59,10 @@ declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
@@ -120,6 +124,14 @@ declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.99c86edb045064f0ff9e.css.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/favicon-32x32.png BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/index.html.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.7c2cb3.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.7c2cb3.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.35c7f4.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.35c7f4.bundle.js.gz BINARY)
|
||||
|
||||
@@ -6,29 +6,29 @@ 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_7c2cb3_bundle_js_gz_start[] asm("_binary_index_7c2cb3_bundle_js_gz_start");
|
||||
extern const uint8_t _index_7c2cb3_bundle_js_gz_end[] asm("_binary_index_7c2cb3_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_7c2cb3_bundle_js_gz_start[] asm("_binary_node_vendors_7c2cb3_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_7c2cb3_bundle_js_gz_end[] asm("_binary_node_vendors_7c2cb3_bundle_js_gz_end");
|
||||
extern const uint8_t _index_35c7f4_bundle_js_gz_start[] asm("_binary_index_35c7f4_bundle_js_gz_start");
|
||||
extern const uint8_t _index_35c7f4_bundle_js_gz_end[] asm("_binary_index_35c7f4_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_35c7f4_bundle_js_gz_start[] asm("_binary_node_vendors_35c7f4_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_35c7f4_bundle_js_gz_end[] asm("_binary_node_vendors_35c7f4_bundle_js_gz_end");
|
||||
const char * resource_lookups[] = {
|
||||
"/css/index.99c86edb045064f0ff9e.css.gz",
|
||||
"/favicon-32x32.png",
|
||||
"/index.html.gz",
|
||||
"/js/index.7c2cb3.bundle.js.gz",
|
||||
"/js/node_vendors.7c2cb3.bundle.js.gz",
|
||||
"/js/index.35c7f4.bundle.js.gz",
|
||||
"/js/node_vendors.35c7f4.bundle.js.gz",
|
||||
""
|
||||
};
|
||||
const uint8_t * resource_map_start[] = {
|
||||
_index_99c86edb045064f0ff9e_css_gz_start,
|
||||
_favicon_32x32_png_start,
|
||||
_index_html_gz_start,
|
||||
_index_7c2cb3_bundle_js_gz_start,
|
||||
_node_vendors_7c2cb3_bundle_js_gz_start
|
||||
_index_35c7f4_bundle_js_gz_start,
|
||||
_node_vendors_35c7f4_bundle_js_gz_start
|
||||
};
|
||||
const uint8_t * resource_map_end[] = {
|
||||
_index_99c86edb045064f0ff9e_css_gz_end,
|
||||
_favicon_32x32_png_end,
|
||||
_index_html_gz_end,
|
||||
_index_7c2cb3_bundle_js_gz_end,
|
||||
_node_vendors_7c2cb3_bundle_js_gz_end
|
||||
_index_35c7f4_bundle_js_gz_end,
|
||||
_node_vendors_35c7f4_bundle_js_gz_end
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/***********************************
|
||||
webpack_headers
|
||||
dist/css/index.99c86edb045064f0ff9e.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.7c2cb3.bundle.js.gz,dist/js/node_vendors.7c2cb3.bundle.js.gz
|
||||
dist/css/index.99c86edb045064f0ff9e.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.35c7f4.bundle.js.gz,dist/js/node_vendors.35c7f4.bundle.js.gz
|
||||
***********************************/
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -148,6 +148,9 @@ menu "Squeezelite-ESP32"
|
||||
int
|
||||
default 21 if MUSE
|
||||
default -1
|
||||
config POWER_GPIO
|
||||
int
|
||||
default -1
|
||||
config JACK_GPIO
|
||||
int
|
||||
default 34 if SQUEEZEAMP || MUSE
|
||||
@@ -405,18 +408,29 @@ menu "Squeezelite-ESP32"
|
||||
default 0
|
||||
endmenu
|
||||
|
||||
menu "Amplifier"
|
||||
visible if !TARGET_LOCKED
|
||||
menu "External amplifier control"
|
||||
visible if !AMP_LOCKED
|
||||
config AMP_GPIO
|
||||
int "Amplifier GPIO"
|
||||
help
|
||||
GPIO to switch on/off amplifier. Set to -1 for no amplifier.
|
||||
GPIO to switch on/off external amplifier. Set to -1 for no amplifier.
|
||||
config AMP_GPIO_LEVEL
|
||||
depends on AMP_GPIO != -1
|
||||
int "Active level(0/1)"
|
||||
default 1
|
||||
endmenu
|
||||
|
||||
menu "Power on/off status"
|
||||
config POWER_GPIO
|
||||
int "Power on/off GPIO"
|
||||
help
|
||||
GPIO that is switched when LMS turns player one. Set to -1 to disable
|
||||
config POWER_GPIO_LEVEL
|
||||
depends on POWER_GPIO != -1
|
||||
int "Active level(0/1)"
|
||||
default 1
|
||||
endmenu
|
||||
|
||||
menu "Speaker Fault"
|
||||
visible if !TARGET_LOCKED
|
||||
config SPKFAULT_GPIO
|
||||
|
||||
@@ -254,6 +254,7 @@ void register_default_nvs(){
|
||||
register_default_string_val("ap_pwd", CONFIG_DEFAULT_AP_PASSWORD);
|
||||
register_default_string_val("bypass_wm", "0");
|
||||
register_default_string_val("equalizer", "");
|
||||
register_default_string_val("loudness", "0");
|
||||
register_default_string_val("actrls_config", "");
|
||||
register_default_string_val("lms_ctrls_raw", "n");
|
||||
register_default_string_val("rotary_config", CONFIG_ROTARY_ENCODER);
|
||||
|
||||
Binary file not shown.
@@ -21,6 +21,18 @@
|
||||
params: ['[% playerid %]', ['squeezeesp32', 'seteq', eqValues.join()]]
|
||||
});
|
||||
}
|
||||
|
||||
var ldValue;
|
||||
this.lastLd = this.lastLd;
|
||||
|
||||
ldValue = Ext.get('pref_loudness').dom.value || 0;
|
||||
|
||||
if (ldValue != this.lastLd) {
|
||||
this.lastLd = ldValue;
|
||||
SqueezeJS.Controller.request({
|
||||
params: ['[% playerid %]', ['squeezeesp32', 'setld', ldValue]]
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
[% END; ELSIF !useExtJS; pageHeaderScripts = BLOCK %]
|
||||
@@ -46,6 +58,23 @@
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var ldValue;
|
||||
this.lastLd = this.lastLd;
|
||||
|
||||
ldValue = $('pref_loudness').value || 0;
|
||||
|
||||
if (ldValue != this.lastLd) {
|
||||
this.lastLd = ldValue;
|
||||
new Ajax.Request('/jsonrpc.js', {
|
||||
method: 'post',
|
||||
postBody: JSON.stringify({
|
||||
id: 1,
|
||||
method: 'slim.request',
|
||||
params: ['[% playerid %]', ['squeezeesp32', 'setld', ldValue]]
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
[% END; END %]
|
||||
@@ -140,6 +169,10 @@
|
||||
[% END %]
|
||||
|
||||
[% WRAPPER settingSection %]
|
||||
[% WRAPPER settingGroup title='Loudness' desc="" %]
|
||||
<input type="text" class="stdedit sliderInput_0_10" name="pref_loudness" id="pref_loudness" value="[% pref_loudness || 0 %]" size="2"">
|
||||
[% END %]
|
||||
|
||||
[% 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 || 0 %]" size="2"">
|
||||
[% END %]
|
||||
|
||||
@@ -115,6 +115,7 @@ sub initPrefs {
|
||||
|
||||
$prefs->client($client)->init( {
|
||||
equalizer => [(0) x 10],
|
||||
loudness => 0,
|
||||
artwork => undef,
|
||||
led_config => 0,
|
||||
led_visualizer => 0,
|
||||
@@ -216,6 +217,14 @@ sub send_equalizer {
|
||||
$client->sendFrame( eqlz => \$data );
|
||||
}
|
||||
|
||||
sub send_loudness {
|
||||
my ($client, $loudness) = @_;
|
||||
|
||||
$loudness ||= $prefs->client($client)->get('loudness') || 0;
|
||||
my $data = pack("c1", $loudness);
|
||||
$client->sendFrame( loud => \$data );
|
||||
}
|
||||
|
||||
sub update_equalizer {
|
||||
my ($client, $value, $index) = @_;
|
||||
return if $client->tone_update;
|
||||
@@ -318,6 +327,7 @@ sub reconnect {
|
||||
$client->pluginData('artwork_md5', '');
|
||||
$client->config_artwork if $client->display->isa("Plugins::SqueezeESP32::Graphics");
|
||||
$client->send_equalizer;
|
||||
$client->send_loudness;
|
||||
}
|
||||
|
||||
# Change the analog output mode between headphone and sub-woofer
|
||||
|
||||
@@ -87,6 +87,8 @@ sub handler {
|
||||
$equalizer = [ splice(@$equalizer, 0, 10) ];
|
||||
$cprefs->set('equalizer', $equalizer);
|
||||
$client->update_tones($equalizer);
|
||||
|
||||
$cprefs->set('loudness', $paramRef->{"pref_loudness"} || 0);
|
||||
}
|
||||
|
||||
if ($client->hasLED) {
|
||||
@@ -107,7 +109,10 @@ sub handler {
|
||||
$paramRef->{'ledVisualModes'} = Plugins::SqueezeESP32::RgbLed::ledVisualModeOptions($client);
|
||||
}
|
||||
|
||||
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer') if $client->can('depth') && $client->depth == 16;
|
||||
if ($client->can('depth') && $client->depth == 16) {
|
||||
$paramRef->{'pref_equalizer'} = $cprefs->get('equalizer');
|
||||
$paramRef->{'pref_loudness'} = $cprefs->get('loudness');
|
||||
}
|
||||
$paramRef->{'player_ip'} = $client->ip;
|
||||
|
||||
require Plugins::SqueezeESP32::FirmwareHelper;
|
||||
|
||||
@@ -34,6 +34,10 @@ $prefs->setChange(sub {
|
||||
$_[2]->send_equalizer;
|
||||
}, 'equalizer');
|
||||
|
||||
$prefs->setChange(sub {
|
||||
$_[2]->send_loudness;
|
||||
}, 'loudness');
|
||||
|
||||
sub initPlugin {
|
||||
my $class = shift;
|
||||
|
||||
@@ -54,6 +58,7 @@ sub initPlugin {
|
||||
|
||||
# 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]);
|
||||
Slim::Control::Request::addDispatch(['squeezeesp32', 'setld', '_ld'], [1, 0, 0, \&setLD]);
|
||||
|
||||
# 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'] ] );
|
||||
@@ -100,4 +105,20 @@ sub setEQ {
|
||||
$client->send_equalizer(\@eqParams);
|
||||
}
|
||||
|
||||
sub setLD {
|
||||
my $request = shift;
|
||||
|
||||
# check this is the correct command.
|
||||
if ($request->isNotCommand([['squeezeesp32'],['setld']])) {
|
||||
$request->setStatusBadDispatch();
|
||||
return;
|
||||
}
|
||||
|
||||
# get our parameters
|
||||
my $client = $request->client();
|
||||
my $loudness = $request->getParam('_ld') || 0;
|
||||
|
||||
$client->send_loudness($loudness);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<name>PLUGIN_SQUEEZEESP32</name>
|
||||
<description>PLUGIN_SQUEEZEESP32_DESC</description>
|
||||
<module>Plugins::SqueezeESP32::Plugin</module>
|
||||
<version>0.362</version>
|
||||
<version>0.501</version>
|
||||
<creator>Philippe</creator>
|
||||
</extensions>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version='1.0' standalone='yes'?>
|
||||
<extensions>
|
||||
<plugins>
|
||||
<plugin version="0.362" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<plugin version="0.501" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
|
||||
<link>https://github.com/sle118/squeezelite-esp32</link>
|
||||
<creator>Philippe</creator>
|
||||
<sha>6c6454b1a6c533a74e1b00b69c5a2143d6df536d</sha>
|
||||
<sha>842ee0f7b8ccaf2e6df4a741565068f9e4c7a27e</sha>
|
||||
<email>philippe_44@outlook.com</email>
|
||||
<desc lang="EN">SqueezeESP32 additional player id (100/101)</desc>
|
||||
<url>http://raw.githubusercontent.com/sle118/squeezelite-esp32/master-v4.3/plugin/SqueezeESP32.zip</url>
|
||||
|
||||
BIN
server_certs/DigiCertGlobalRootCA.crt.49
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.49
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.50
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.50
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.51
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.51
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.52
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.52
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.21
Normal file
BIN
server_certs/r2m01.cer.21
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.22
Normal file
BIN
server_certs/r2m01.cer.22
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.23
Normal file
BIN
server_certs/r2m01.cer.23
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.24
Normal file
BIN
server_certs/r2m01.cer.24
Normal file
Binary file not shown.
Reference in New Issue
Block a user