Compare commits

...

84 Commits

Author SHA1 Message Date
philippe44
105e800cc1 fix podcast on Spotify - release 2023-10-04 15:51:55 -07:00
github-actions
ab09f009f7 Update prebuilt objects [skip actions] 2023-10-03 03:09:28 +00:00
Sébastien
9c179adf85 Update CHANGELOG release 2023-10-02 23:07:00 -04:00
philippe44
719b289659 update cspot 2023-10-02 19:06:59 -07:00
Sébastien
eb7df4a5e9 attempt at fixing build parameter logic. [skip actions] 2023-10-02 07:50:39 -04:00
Sebastien L
9a54239323 UI build should be manually triggered 2023-10-01 21:37:25 -04:00
Sébastien
b790156be0 Update CHANGELOG 2023-10-01 16:48:27 -04:00
Sébastien
f87b3adec2 Update CHANGELOG-release 2023-10-01 16:38:05 -04:00
Sebastien L
35d42f2096 roll back webapp 2023-10-01 16:23:01 -04:00
Sébastien
d2d0cadeed Make UI build optional 2023-09-30 23:33:04 -04:00
github-actions
cd76619e96 Update prebuilt objects [skip actions] 2023-09-30 01:41:41 +00:00
philippe44
781699362f add CHANGELOG (at last) - release 2023-09-29 18:37:47 -07:00
philippe44
c4dbf60cb6 Update README.md 2023-09-28 17:23:06 -07:00
philippe44
3dd7b4b02c Update README.md 2023-09-28 17:19:35 -07:00
philippe44
83c1a2b8e0 Update README.md 2023-09-28 17:18:59 -07:00
philippe44
e8021c38f0 Update README.md 2023-09-28 17:17:58 -07:00
philippe44
71bb57f1eb Update README.md 2023-09-28 17:16:40 -07:00
philippe44
7eb4b218e3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-28 17:11:05 -07:00
philippe44
953d1657f9 add soft on/off schematics 2023-09-28 17:09:56 -07:00
philippe44
b21a143196 Delete 269289674-4d8eb4f6-0d88-4dab-858f-c3de3ec33139.png 2023-09-28 16:54:50 -07:00
philippe44
898f1d92ed Add files via upload 2023-09-28 16:53:13 -07:00
philippe44
f6fd11783c Merge pull request #313 from wizmo2/rmt-esp32s3
Support for ESP32S3 RMT channel restrictions
2023-09-28 10:33:11 -07:00
Wizmo2
f33cb569ce rmt helper to function 2023-09-28 07:34:58 -04:00
philippe44
f5d6f26c01 Update README.md 2023-09-27 19:42:02 -07:00
philippe44
8df6b853e6 Merge pull request #312 from wizmo2/st7789-mod
Allow offset on ST7789
2023-09-27 19:41:21 -07:00
philippe44
f7c0107d40 Merge pull request #314 from wizmo2/led_vu_status
Add battery status to led_vu
2023-09-27 19:40:43 -07:00
philippe44
5068309d25 manage Spotify credentials 2023-09-27 19:36:38 -07:00
philippe44
506a5aaf7a Update README.md 2023-09-27 19:22:50 -07:00
philippe44
fe409730e0 Update README.md 2023-09-27 18:13:31 -07:00
Wizmo2
4d6cfaca1c format changes 2023-09-27 06:25:30 -04:00
Wizmo2
69e61ce451 add battery status to vu meter effect 2023-09-25 20:57:37 -04:00
Wizmo2
60bd591bf8 support for c3 and s3 channel restrictions 2023-09-25 19:32:13 -04:00
Wizmo2
3ea26a0c6f allow offset on ST7789 2023-09-25 17:47:26 -04:00
philippe44
53fa83b2dd cspot dsconnect when paused handling 2023-09-24 00:18:30 -07:00
philippe44
90f53db953 http_download needs a bigger stack for cspot which required to move it to EXTRAM 2023-09-23 22:20:01 -07:00
philippe44
b413780048 some small speed further optimization to 24 bits SPDIF 2023-09-22 22:38:37 -07:00
philippe44
456f16fc79 Update README.md 2023-09-22 22:32:05 -07:00
philippe44
04e2917351 Update README.md 2023-09-22 22:29:58 -07:00
philippe44
dc9e1191a2 move sleep init at the very end of boot (all must be initialized) 2023-09-22 15:27:27 -07:00
philippe44
72a8fb2249 Merge pull request #307 from UrbanLienert/spdif-24bit
24bit SPDIF output
2023-09-22 15:26:29 -07:00
UrbanLienert
f409a9ee28 made vucp static 2023-09-22 08:06:23 +02:00
UrbanLienert
b85cf98cdf rmoved duplicated lines 2023-09-21 16:24:20 +02:00
UrbanLienert
e85c967220 changed 16bit as well 2023-09-20 18:51:13 +02:00
philippe44
ce21ff1b76 Update README.md 2023-09-18 22:34:32 -07:00
philippe44
7c71fb6a65 Update README.md 2023-09-18 18:40:30 -07:00
philippe44
bb22d4ea02 Update README.md 2023-09-18 18:39:37 -07:00
philippe44
e0e749fb5b add (some) spurious IR detection capability 2023-09-18 18:34:03 -07:00
UrbanLienert
c650bc7658 new version fix 2023-09-18 22:39:10 +02:00
philippe44
a76ce3f344 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-18 11:40:05 -07:00
philippe44
1d059be001 clz for 64 bits requies special version( wake bitmap) 2023-09-18 11:40:01 -07:00
UrbanLienert
32195b50ba 24bit spdif output 2023-09-18 17:13:58 +02:00
philippe44
a7539a5332 Update README.md 2023-09-17 20:38:44 -07:00
philippe44
450ebae399 Update README.md 2023-09-17 20:37:36 -07:00
philippe44
d238063c49 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-17 20:00:15 -07:00
philippe44
58f2e4488b don't count failed IR as activity 2023-09-17 20:00:11 -07:00
philippe44
b83e2722c0 Update README.md 2023-09-16 23:17:19 -07:00
philippe44
96a3f8aab0 Update README.md 2023-09-16 19:48:17 -07:00
philippe44
66b88d186a fix some buttons snafu & power off led on suspend 2023-09-16 19:42:31 -07:00
philippe44
bb185d76dc add buttons/rotary/ir/BT sink in sleep + battery low-voltage 2023-09-16 17:55:41 -07:00
philippe44
f4c0a91e84 Merge pull request #303 from wizmo2/fix_led_vu
Fix led_vu
2023-09-16 17:24:46 -07:00
Wizmo2
2ecf2e6098 hacked led_strip 2023-09-16 17:13:07 -04:00
Wizmo2
f32e4de84b static declaration in function 2023-09-16 16:47:40 -04:00
Wizmo2
55303ec1b3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-16 16:46:27 -04:00
philippe44
effc574e50 Update README.md 2023-09-16 00:35:09 -07:00
philippe44
3941a26b67 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-16 00:24:49 -07:00
philippe44
b47074e668 Update README.md 2023-09-15 21:32:46 -07:00
philippe44
822de92df1 Update README.md 2023-09-15 21:31:54 -07:00
philippe44
6bd778c7c6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-15 21:26:36 -07:00
philippe44
28ecad4652 ACTRLS_SLEEP should be last (indexed array) and red led is not default for parsing 2023-09-15 21:26:33 -07:00
philippe44
ddd6bddde7 Update README.md 2023-09-15 20:46:41 -07:00
philippe44
f4e899fc60 add option to maintain some RTC GPIO pull-up/down 2023-09-15 20:38:47 -07:00
philippe44
c61ff05081 take into account case with no wake option 2023-09-15 18:50:26 -07:00
philippe44
c4df0c93f9 Update README.md 2023-09-15 18:47:23 -07:00
philippe44
fae09ba29e Update README.md 2023-09-15 18:43:02 -07:00
Wizmo2
fd0c38c49f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-15 20:28:18 -04:00
philippe44
a83f14113e add sleep option + potentially fix led_vu issue 2023-09-15 17:18:06 -07:00
Wizmo2
7f0b411dac fix endings 2023-09-15 20:15:53 -04:00
Wizmo2
3350a8dbc7 restore static struct for led_vu 2023-09-15 20:08:31 -04:00
philippe44
a2eddb5411 Update README.md 2023-09-15 16:50:20 -07:00
philippe44
65ff5f7c2a Update README.md 2023-09-14 19:26:53 -07:00
philippe44
5ed2f6d03e Update README.md 2023-09-14 19:23:56 -07:00
philippe44
d8bd588982 Update README.md 2023-09-14 19:21:30 -07:00
philippe44
e4481a95f9 Update README.md 2023-09-14 19:21:17 -07:00
github-actions
992f5c361c Update prebuilt objects [skip actions] 2023-09-13 23:03:19 +00:00
82 changed files with 1175 additions and 416 deletions

View File

@@ -34,12 +34,13 @@ jobs:
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Set build flags
id: build_flags
run: |
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
[ ${{github.event.inputs.ui_build}} ] && ui_build_option="--ui_build" || ui_build_option=""
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
[ "${{github.event.inputs.ui_build}}" == "1" ] && ui_build_option="--ui_build" || ui_build_option=""
[ "${{github.event.inputs.release_build}}" == "true" ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> $GITHUB_ENV
echo "release_build_option=$release_build_option" >> $GITHUB_ENV
echo "Dumping environment"
@@ -49,7 +50,7 @@ jobs:
# --mock - to mock the compilation part - this is to be used for testing only
# --force - to force a release build even if the last commit message doesn't contain the word "release"
# --ui_build - to force a ui_build even if the last commit message doesn't contain "[ui-build]"
build_tools.py build_flags $ui_build_option $release_build_option
build_tools.py build_flags $ui_build_option $release_build_option
- name: Show Build Flags
run: |
echo "Running with the following options"

12
CHANGELOG Normal file
View File

@@ -0,0 +1,12 @@
2023-10-02
- update cspot
2023-09-29
- sleep mechanism
- spotify can store credentials so that zeroconf is optional and players are always registered
- add battery to led_vu (see credits)
- spdif can do 24 bits (see credits)
- rmt fixes
- airplay & spotify artwork fixes
- airplay stability improvments
- fix UI text color

View File

@@ -7,7 +7,7 @@ Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 an
- 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 directly 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)) - please read carefully [this](#spotify)
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.
@@ -69,7 +69,7 @@ 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.
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).
@@ -82,6 +82,8 @@ NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hard
- dac_config: `model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0`
- spdif_config: `bck=33,ws=25,do=15`
The IR can be used as a wake-up signal using (setting `sleep_config` with `wake=0:0`). It's a pull-up so it stays at 1 when not receiving anything which means it cannot be used in conjuction with other wake-up IOs. See [Sleeping](#sleeping) for more details regarding the limitation of waking-up upon multiple inputs.
### MuseLuxe
This portable battery-powered [speaker](https://raspiaudio.com/produit/esp-muse-luxe) is compatible with squeezelite-esp32 for which there is a dedicated build supplied with every update. If you want to rebuild, use the `squeezelite-esp32-Muse-sdkconfig.defaults` configuration file.
@@ -211,7 +213,7 @@ bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For named configurations, this is ignored
To optimize speed, a bit-manipulation trick is used and as a result, the bit depth is limited to 20 bits, even in 32 bits mode. As said before, this is more than enough for any human ear. In theory, it could be extended up to 23 bits but I don't see the need. Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
The maximum bit depth is 24 bits, even in 32 bits mode (this a SPDIF limitation - thank @UrbanLienert for theupdate from 20 to 24 bit). Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
If you want coax, you can also use a poor-man's trick to generate signal from a 3.3V GPIO. All that does is dividing the 3.3V to generate a 0.6V peak-to-peak and then remove DC
```
@@ -227,7 +229,7 @@ Ground -------------------------- coax signal ground
The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735[:x=<offset>][:y=<offset>]|ST7789|ILI9341[:16|18][,rotate]]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
@@ -393,9 +395,11 @@ ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_P
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
BCTRLS_PS1, BCTRLS_PS2, BCTRLS_PS3, BCTRLS_PS4, BCTRLS_PS5, BCTRLS_PS6, BCTRLS_PS7, BCTRLS_PS8, BCTRLS_PS9, BCTRLS_PS10,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_SLEEP,
```
Note that ACTRLS_SLEEP is not an actual button that can be sent to LMS, but it's a hook to activate deep sleep mode (see [Sleeping](#sleeping)).
One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
For example a config named "buttons" :
@@ -504,7 +508,36 @@ 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)
# Configuration
### Sleeping
The esp32 can be put in deep sleep mode to save some power. How much really depends on the connected periperals, so best is to do your own measures. Waking-up from deep sleep is the equivalent of a reboot, but as the chip takes a few seconds to connect, it's still an efficient process.
The esp32 can enter deep sleep after an audio inactivity timeout, after a button has been pressed, after a GPIO is set to a given level (there is a subtle difference, see below) or if the battery reaches a threashold. It wakes up only on some GPIO events. Note that *all* GPIO are isolated when sleeping (unless they are set with the `rtc`option) so you can not assume anything about their value, except that they will not drain current. The `rtc` option allows to keep some GPIO (from the RTC domain only) either pulled up or down. This can be useful if you want to keep some periperal active, for example a GPIO expander whose interrupt will be used to wake-up the system.
The NVS parameter `sleep_config` is mostly used for setting sleep conditions
```
[delay=<mins>][,sleep=<gpio>[:0|1]][,wake=<gpio>[:0|1][|<gpio>[:0|1]...][,rtc=<gpio>[:0|1][|<gpio>[:0|1]...][,batt=<voltage>][,spurious=<mins>]
```
- delay: inactivity in **minutes** before going to sleep
- spurious: when using IR, wake-up can be triggered by any activity on the allocated GPIO, hence other remotes may cause unwanted wake-up. This sets (in **minutes** - default is 1) an inactivity delay after which sleep resumes.
- sleep: GPIO that will put the system into sleep and it can be a level 0 or 1.
- wake: **list** of GPIOs that with cause it to wake up (reboot) with their respective values. In such list, GPIO's are separated by an actual '|'.
- batt: threshold in **volts** under which the system will enter into sleep.
The battery voltage is measured every 10 seconds and 30 values are averaged before producing a result. The result must be 3 times below the threshold to enter sleep, so it takes a total of 10\*30\*3 = 15 minutes.
Be mindful that if the same GPIO is used to go to sleep and wakeup with the *same* level (in other word it's a transition/edge that triggers the action) the above will not work and the esp32 will immediately restart. In such case, you case use a button definition. The benefit of buttons is that not only can you re-use one actual button (e.g. 'stop') to make it the sleep trigger (using a long-press or a shift-press) but by selecting the ACTRLS_SLEEP action upon 'release', you can got to sleep upon release (1-0-1 transition) but also wake up upon another press (0 level applied on GPIO) because you only go to sleep *after* the GPIO returned to 1.
Please see [buttons](#buttons) for detailed syntax.
The option to use multiple GPIOs is very limited on esp32 and the esp-idf 4.3.x we are using: it is only possible to wake-up when **any** of the defined GPIO is set to 1. The fact that you can specify different levels in the wake list is irrelevant for now, it's just a provision for future upgrades to more recent versions of esp-idf.
**Only the following GPIOs can be used to wake-up the esp32**
- ESP32: 0, 2, 4, 12-15, 25-27, 32-39;
- ESP32-S3: 0-21.
Some have asked for a soft power on/off option. Although this is not built-in, it's easy to create yours as long as the regulator/power supply of the board can be controlled by Vcc or GND. Depending on how it is active, add a pull-up/down resistor to the regulator's control and connect it also to one GPIO of the esp32. Then using set_GPIO, set that GPIO to Vcc or GND. Use a hardware button that forces the regulator on with a pull- up/down and once the esp32 has booted, it will force the GPIO to the desired value maintaining the board on by software. To power it off by software, just use the deep sleep option which will suspend all GPIO hence switching off the regulator.
# Software configuration
## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
@@ -525,6 +558,15 @@ At this point, the device should have disabled its built-in access point and sho
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times)
- You can enable accessto NVS parameters under 'credits'
## Spotify
By default, SqueezeESP32 will use ZeroConf to advertise its Spotify capabilties. This means that until at least one local Spotify Connect application controllers discovers and connects to it, SqueezeESP32 will not be registered to Spotify servers. As a consequence, Spotify's WebAPI will not be able to see it (for example, Home Assistant services will miss it). Once you are connected to it using for example Spotify Desktop app, it will be registered and displayed everywhere.
If you want the player to be registered at start-up, you need to disable the ZeroConf option using the WebUI or `cspot_config::ZeroConf`. In that mode, the first time you run SqueezeESP32, it will be in ZeroConf mode and when you connect to it using a controller for the firt time, it receives and store credentials that will be used next time (after reboot).
Set ZeroConf to 1 will always force ZeroConf mode to be used.
The ZeroConf mode consumes less memory as it uses the built-in HTTP and mDNS servers to broadcast its capabilities. A Spotify controller will then discover these and trigger the SqueezeESP32 Spotify stack (cspot) to start. When the controller disconnects, the stack is shut down. In non-ZeroConf mode, the stack starts immediately (providing stored credentials are valid) and always run - a disconnect will not shut it down.
## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
@@ -618,5 +660,11 @@ If you have already cloned the repository and you are getting compile errors on
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=48000 and unset VAR_ARRAYS in config.h
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...
# Hardware tips
There is a possibility to have a software on/off where a temporary switch can power-up the esp32 which then will auto-sustain its power. Depending on the selected hardware, it a can also include a power-off by using a long press on the same button.
The auto-power is simply acheived by using `setGPIO` and forcing a GPIO to Vcc or GND and the sustain on/off requires a button creation whose longpress is an ACTRLS_SLEEP action (see also the [Sleeping](#sleeping) section). Credits [Renber78](http://github.com/Renber78) for schedmatics below
![alt text](https://github.com/sle118/squeezelite-esp32/blob/7eb4b218e31aa4692c5280fbec4619f690032c4a/Soft%20Power.png)
# Footnotes
(1) SPDIF is made by tricking the I2S bus but this consumes a fair bit of CPU as it multiplies by four the throughput on the i2s bus. To optimize some computation, the parity of the spdif frames must always be 0, so at least one bit has to be available to force it. As SPDIF samples are 20+4 bits length maximum, the LSB is used for that purpose, so the bit 24 is randomly toggling. It does not matter for 16 bits samples but it has been chosen to truncate the last 4 bits for 24 bits samples. I'm sure that some smart dude can further optimize spdif_convert() and use the user bit instead. You're welcome to do a PR but, as said above, I (philippe44) am not interested by 24 bits mental illness :-) and I've already made an effort to provide 20 bits which already way more what's needed :-)

BIN
Soft Power.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -948,7 +948,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -907,7 +907,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -918,7 +918,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -289,10 +289,8 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->Model = Model;
if (Model == ST7735) {
sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height);
sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width);
}
sscanf(Driver, "%*[^:]%*[^x]%*[^=]=%hu", &Private->Offset.Height);
sscanf(Driver, "%*[^:]%*[^y]%*[^=]=%hu", &Private->Offset.Width);
if (Depth == 18) {
Device->Mode = GDS_RGB666;

View File

@@ -15,6 +15,7 @@
#include "platform_config.h"
#include "tools.h"
#include "display.h"
#include "services.h"
#include "gds.h"
#include "gds_default_if.h"
#include "gds_draw.h"
@@ -73,7 +74,9 @@ static const char *known_drivers[] = {"SH1106",
"ILI9341",
NULL
};
static void displayer_task(void *args);
static void display_sleep(void);
struct GDS_Device *display;
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
@@ -174,11 +177,21 @@ void display_init(char *welcome) {
if (height <= 64 && width > height * 2) displayer.artwork.offset = width - height - ARTWORK_BORDER;
PARSE_PARAM(displayer.metadata_config, "artwork", ':', displayer.artwork.fit);
}
// and finally register ourselves to power off upon deep sleep
services_sleep_setsuspend(display_sleep);
}
free(config);
}
/****************************************************************************************
*
*/
static void display_sleep(void) {
GDS_DisplayOff(display);
}
/****************************************************************************************
* This is not thread-safe as displayer_task might be in the middle of line drawing
* but it won't crash (I think) and making it thread-safe would be complicated for a

View File

@@ -25,6 +25,8 @@ static const char * TAG = "bt_app_source";
static const char * BT_RC_CT_TAG="RCCT";
extern int32_t output_bt_data(uint8_t *data, int32_t len);
extern void output_bt_tick(void);
extern void output_bt_stop(void);
extern void output_bt_start(void);
extern char* output_state_str(void);
extern bool output_stopped(void);
extern bool is_recovery_running;
@@ -803,6 +805,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(TAG,"a2dp media started successfully.");
output_bt_start();
set_a2dp_media_state(APP_AV_MEDIA_STATE_STARTED);
} else {
// not started succesfully, transfer to idle state
@@ -831,6 +834,7 @@ static void bt_app_av_media_proc(uint16_t event, void *param)
if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP &&
a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) {
ESP_LOGI(TAG,"a2dp media stopped successfully...");
output_bt_stop();
set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE);
} else {
ESP_LOGI(TAG,"a2dp media stopping...");

View File

@@ -38,21 +38,12 @@ struct led_color_t {
};
struct led_strip_t {
const enum rgb_led_type_t rgb_led_type;
enum rgb_led_type_t rgb_led_type; // should be const, but workaround needed for initialization
uint32_t led_strip_length;
// RMT peripheral settings
rmt_channel_t rmt_channel;
/*
* Interrupt table is located in soc.h
* As of 11/27/16, reccomended interrupts are:
* 9, 12, 13, 17, 18, 19, 20, 21 or 23
* Ensure that the same interrupt number isn't used twice
* across all libraries
*/
int rmt_interrupt_num;
gpio_num_t gpio; // Must be less than GPIO_NUM_33
struct led_color_t *led_strip_working;

View File

@@ -13,7 +13,6 @@
* Driver does support other led device. Maybe look at supporting in future.
* The VU refresh rate has been decreaced (100->75) to optimize animation of spin dial. Could make
* configurable like text scrolling (or use the same value)
* Look at reserving a status led within the effects. (may require nvs setting for center or end position)
* Artwork function, but not released as very buggy and not really practical
*/
@@ -22,13 +21,18 @@
#include "esp_log.h"
#include "globdefs.h"
#include "monitor.h"
#include "led_strip.h"
#include "platform_config.h"
#include "led_vu.h"
static const char *TAG = "led_vu";
#define LED_VU_STACK_SIZE (3*1024)
static void (*battery_handler_chain)(float value, int cells);
static void battery_svc(float value, int cells);
static int battery_status = 0;
#define LED_VU_STACK_SIZE (3*1024)
#define LED_VU_PEAK_HOLD 6U
@@ -36,9 +40,13 @@ static const char *TAG = "led_vu";
#define LED_VU_DEFAULT_LENGTH 19
#define LED_VU_MAX_LENGTH 255
#define LED_VU_STATUS_GREEN 75
#define LED_VU_STATUS_RED 25
#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;
static EXT_RAM_ATTR struct {
int gpio;
@@ -46,7 +54,7 @@ static EXT_RAM_ATTR struct {
int vu_length;
int vu_start_l;
int vu_start_r;
int vu_odd;
int vu_status;
} strip;
static int led_addr(int pos ) {
@@ -55,6 +63,13 @@ static int led_addr(int pos ) {
return pos;
}
static void battery_svc(float value, int cells) {
battery_status = battery_level_svc();
ESP_LOGI(TAG, "Called for battery service with volt:%f cells:%d status:%d", value, cells, battery_status);
if (battery_handler_chain) battery_handler_chain(value, cells);
}
/****************************************************************************************
* Initialize the led vu strip if configured.
*
@@ -80,29 +95,41 @@ void led_vu_init()
ESP_LOGI(TAG, "led_vu configuration invalid");
goto done;
}
battery_handler_chain = battery_handler_svc;
battery_handler_svc = battery_svc;
battery_status = battery_level_svc();
if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH;
// initialize vu settings
//strip.vu_length = (strip.length % 2) ? strip.length / 2 : (strip.length - 1) / 2;
strip.vu_length = (strip.length - 1) / 2;
strip.vu_start_l = strip.vu_length;
strip.vu_start_r = strip.vu_start_l + 1;
strip.vu_odd = strip.length - 1;
// initialize vu meter settings
if (strip.length < 10) {
// single bar for small strips
strip.vu_length = strip.length;
strip.vu_start_l = 0;
strip.vu_start_r = strip.vu_start_l;
strip.vu_status = 0;
} else {
strip.vu_length = (strip.length - 1) / 2;
strip.vu_start_l = (strip.length % 2) ? strip.vu_length -1 : strip.vu_length;
strip.vu_start_r = strip.vu_length + 1;
strip.vu_status = strip.vu_length;
}
ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status);
// create driver configuration
struct led_strip_t led_strip_config = { .rgb_led_type = RGB_LED_TYPE_WS2812 };
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++;
led_strip_config.rmt_channel = RMT_NEXT_TX_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 on channek:%d", strip.gpio, strip.length, led_strip_config.rmt_channel);
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d on channel:%d", strip.gpio, strip.length, led_strip_config.rmt_channel);
} else {
ESP_LOGE(TAG, "led_vu init failed");
goto done;
@@ -295,7 +322,11 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) {
static int decay_r = 0;
if (!led_display) return;
// single bar
if (strip.vu_start_l == strip.vu_start_r) {
vu_r = (vu_l + vu_r) / 2;
vu_l = 0;
}
// scale vu samples to length
vu_l = vu_l * strip.vu_length / bright;
@@ -356,6 +387,14 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) {
g = (g < step) ? 0 : g - step;
}
// show battery status
if (battery_status > LED_VU_STATUS_GREEN)
led_strip_set_pixel_rgb(led_display, strip.vu_status, 0, bright, 0);
else if (battery_status > LED_VU_STATUS_RED)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright/2, bright/2, 0);
else if (battery_status > 0)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright, 0, 0);
led_strip_show(led_display);
}

View File

@@ -13,14 +13,14 @@ esp_err_t store_nvs_value_len(nvs_type_t type, const char *key, void * data, siz
esp_err_t store_nvs_value(nvs_type_t type, const char *key, void * data);
esp_err_t get_nvs_value(nvs_type_t type, const char *key, void*value, const uint8_t buf_size);
void * get_nvs_value_alloc(nvs_type_t type, const char *key);
void * get_nvs_value_alloc_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, size_t * size);
esp_err_t erase_nvs_for_partition(const char * partition, const char * namespace,const char *key);
esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * namespace,nvs_type_t type, const char *key, const void * data,size_t data_len);
void * get_nvs_value_alloc_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, size_t * size);
esp_err_t erase_nvs_for_partition(const char * partition, const char * ns,const char *key);
esp_err_t store_nvs_value_len_for_partition(const char * partition,const char * ns,nvs_type_t type, const char *key, const void * data,size_t data_len);
esp_err_t erase_nvs(const char *key);
void print_blob(const char *blob, size_t len);
const char *type_to_str(nvs_type_t type);
nvs_type_t str_to_type(const char *type);
esp_err_t erase_nvs_partition(const char * partition, const char * namespace);
esp_err_t erase_nvs_partition(const char * partition, const char * ns);
void erase_settings_partition();
#ifdef __cplusplus
}

View File

@@ -131,6 +131,7 @@ static struct {
struct arg_str *deviceName;
// struct arg_int *volume;
struct arg_int *bitrate;
struct arg_int *zeroConf;
struct arg_end *end;
} cspot_args;
static struct {
@@ -656,6 +657,9 @@ static int do_cspot_config(int argc, char **argv){
if(cspot_args.bitrate->count>0){
cjson_update_number(&cspot_config,cspot_args.bitrate->hdr.longopts,cspot_args.bitrate->ival[0]);
}
if(cspot_args.zeroConf->count>0){
cjson_update_number(&cspot_config,cspot_args.zeroConf->hdr.longopts,cspot_args.zeroConf->ival[0]);
}
if(!nerrors ){
fprintf(f,"Storing cspot parameters.\n");
@@ -668,6 +672,9 @@ static int do_cspot_config(int argc, char **argv){
if(cspot_args.bitrate->count>0){
fprintf(f,"Bitrate changed to %u\n",cspot_args.bitrate->ival[0]);
}
if(cspot_args.zeroConf->count>0){
fprintf(f,"ZeroConf changed to %u\n",cspot_args.zeroConf->ival[0]);
}
}
if(!nerrors ){
fprintf(f,"Done.\n");
@@ -853,6 +860,10 @@ cJSON * cspot_cb(){
if(cspot_values){
cJSON_AddNumberToObject(values,cspot_args.bitrate->hdr.longopts,cJSON_GetNumberValue(cspot_values));
}
cspot_values = cJSON_GetObjectItem(cspot_config,cspot_args.zeroConf->hdr.longopts);
if(cspot_values){
cJSON_AddNumberToObject(values,cspot_args.zeroConf->hdr.longopts,cJSON_GetNumberValue(cspot_values));
}
cJSON_Delete(cspot_config);
return values;
@@ -1286,6 +1297,7 @@ static void register_known_templates_config(){
static void register_cspot_config(){
cspot_args.deviceName = arg_str1(NULL,"deviceName","","Device Name");
cspot_args.bitrate = arg_int1(NULL,"bitrate","96|160|320","Streaming Bitrate (kbps)");
cspot_args.zeroConf = arg_int1(NULL,"zeroConf","0|1","Force use of ZeroConf");
// cspot_args.volume = arg_int1(NULL,"volume","","Spotify Volume");
cspot_args.end = arg_end(1);
const esp_console_cmd_t cmd = {

View File

@@ -19,6 +19,7 @@
#include "buttons.h"
#include "platform_config.h"
#include "accessors.h"
#include "services.h"
#include "audio_controls.h"
typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
@@ -61,7 +62,7 @@ static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(
EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT),
EP(BCTRLS_PS0),EP(BCTRLS_PS1),EP(BCTRLS_PS2),EP(BCTRLS_PS3),EP(BCTRLS_PS4),EP(BCTRLS_PS5),EP(BCTRLS_PS6),EP(BCTRLS_PS7),EP(BCTRLS_PS8),EP(BCTRLS_PS9),
EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH),
EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH), EP(ACTRLS_SLEEP),
""} ;
static const char * TAG = "audio controls";
@@ -170,13 +171,6 @@ static void control_handler(void *client, button_event_e event, button_press_e p
actrls_config_t *key = (actrls_config_t*) client;
actrls_action_detail_t action_detail;
// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
if (current_raw_controls) {
ESP_LOGD(TAG, "calling action %u in raw mode", key->normal[0].action);
if (current_controls[key->normal[0].action]) (*current_controls[key->normal[0].action])(event == BUTTON_PRESSED);
return;
}
switch(press) {
case BUTTON_NORMAL:
if (long_press) action_detail = key->longpress[event == BUTTON_PRESSED ? 0 : 1];
@@ -195,7 +189,15 @@ static void control_handler(void *client, button_event_e event, button_press_e p
// stop here if control hook served the request
if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
if (current_raw_controls && action_detail.action != ACTRLS_SLEEP) {
actrls_action_e action = key->normal[0].action != ACTRLS_NONE ? key->normal[0].action : key->normal[1].action;
ESP_LOGD(TAG, "calling action %u in raw mode", action);
if (action != ACTRLS_NONE && current_controls[action]) current_controls[action](event == BUTTON_PRESSED);
return;
}
// otherwise process using configuration
if (action_detail.action == ACTRLS_REMAP) {
// remap requested
@@ -216,7 +218,10 @@ static void control_handler(void *client, button_event_e event, button_press_e p
} else {
ESP_LOGE(TAG,"Invalid profile name %s. Cannot remap buttons",action_detail.name);
}
} else if (action_detail.action != ACTRLS_NONE) {
} else if (action_detail.action == ACTRLS_SLEEP) {
ESP_LOGI(TAG, "special sleep button pressed");
services_sleep_activate(SLEEP_ONKEY);
} else if (action_detail.action != ACTRLS_NONE) {
ESP_LOGD(TAG, "calling action %u", action_detail.action);
if (current_controls[action_detail.action]) (*current_controls[action_detail.action])(event == BUTTON_PRESSED);
}

View File

@@ -11,11 +11,12 @@
#include "buttons.h"
// BEWARE: this is the index of the array of action below (change actrls_action_s as well!)
typedef enum { ACTRLS_NONE = -1, ACTRLS_POWER,ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
typedef enum { ACTRLS_NONE = -1, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
BCTRLS_PS0,BCTRLS_PS1,BCTRLS_PS2,BCTRLS_PS3,BCTRLS_PS4,BCTRLS_PS5,BCTRLS_PS6,BCTRLS_PS7,BCTRLS_PS8,BCTRLS_PS9,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_SLEEP,
ACTRLS_REMAP, ACTRLS_MAX
} actrls_action_e;

View File

@@ -40,7 +40,7 @@ static struct {
.cells = 2,
};
void (*battery_handler_svc)(float value);
void (*battery_handler_svc)(float value, int cells);
/****************************************************************************************
*
@@ -66,7 +66,7 @@ static void battery_callback(TimerHandle_t xTimer) {
if (++battery.count == 30) {
battery.avg = battery.sum / battery.count;
battery.sum = battery.count = 0;
if (battery_handler_svc) (battery_handler_svc)(battery.avg);
if (battery_handler_svc) (battery_handler_svc)(battery.avg, battery.cells);
ESP_LOGI(TAG, "Voltage %.2fV", battery.avg);
}
}

View File

@@ -23,12 +23,14 @@
#include "driver/rmt.h"
#include "gpio_exp.h"
#include "buttons.h"
#include "services.h"
#include "rotary_encoder.h"
#include "globdefs.h"
static const char * TAG = "buttons";
static EXT_RAM_ATTR int n_buttons;
static EXT_RAM_ATTR uint32_t buttons_idle_since;
#define BUTTON_STACK_SIZE 4096
#define MAX_BUTTONS 32
@@ -156,18 +158,29 @@ static void buttons_handler(struct button_s *button, int level) {
}
}
/****************************************************************************************
* Get inactivity callback
*/
static uint32_t buttons_idle_callback(void) {
return pdTICKS_TO_MS(xTaskGetTickCount()) - buttons_idle_since;
}
/****************************************************************************************
* Tasks that calls the appropriate functions when buttons are pressed
*/
static void buttons_task(void* arg) {
ESP_LOGI(TAG, "starting button tasks");
ESP_LOGI(TAG, "starting button tasks");
buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
services_sleep_setsleeper(buttons_idle_callback);
while (1) {
QueueSetMemberHandle_t xActivatedMember;
bool active = true;
// wait on button, rotary and infrared queues
if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
if (xActivatedMember == button_queue) {
struct button_s button;
button_event_e event;
@@ -221,8 +234,11 @@ static void buttons_task(void* arg) {
ROTARY_RIGHT : ROTARY_LEFT, false);
} else {
// this is IR
infrared_receive(infrared.rb, infrared.handler);
active = infrared_receive(infrared.rb, infrared.handler);
}
// mark the last activity
if (active) buttons_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
}
}

View File

@@ -13,11 +13,15 @@
#define I2C_SYSTEM_PORT 1
#define SPI_SYSTEM_HOST SPI2_HOST
#define RMT_NEXT_TX_CHANNEL() rmt_system_base_tx_channel++;
#define RMT_NEXT_RX_CHANNEL() rmt_system_base_rx_channel--;
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;
extern int rmt_system_base_tx_channel;
extern int rmt_system_base_rx_channel;
typedef struct {
int timer, base_channel, max;
} pwm_system_t;

View File

@@ -22,6 +22,8 @@ static const char* TAG = "IR";
#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 */
static int8_t ir_gpio = -1;
/**
* @brief IR device type
*
@@ -446,14 +448,14 @@ err:
/****************************************************************************************
*
*/
void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
bool 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);
bool decoded = false;
if (item) {
uint32_t addr, cmd;
bool repeat = false;
bool decoded = false;
rx_size /= 4; // one RMT = 4 Bytes
@@ -474,14 +476,22 @@ void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
// after parsing the data, return spaces to ringbuffer.
vRingbufferReturnItem(rb, (void*) item);
}
return decoded;
}
/****************************************************************************************
*
*/
int8_t infrared_gpio(void) {
return ir_gpio;
};
/****************************************************************************************
*
*/
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) {
int rmt_channel = rmt_system_base_channel++;
int rmt_channel = RMT_NEXT_RX_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);
@@ -489,6 +499,7 @@ void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) {
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);
ir_gpio = gpio;
// get RMT RX ringbuffer
rmt_get_ringbuf_handle(rmt_channel, rb);

View File

@@ -15,6 +15,7 @@
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);
bool infrared_receive(RingbufHandle_t rb, infrared_handler handler);
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode);
int8_t infrared_gpio(void);

View File

@@ -24,6 +24,7 @@
#include "led.h"
#include "globdefs.h"
#include "accessors.h"
#include "services.h"
#define MAX_LED 8
#define BLOCKTIME 10 // up to portMAX_DELAY
@@ -240,7 +241,7 @@ bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type
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++;
if (led_rmt_channel < 0) led_rmt_channel = RMT_NEXT_TX_CHANNEL();
leds[idx].channel = led_rmt_channel;
leds[idx].bright = bright > 0 ? bright : 100;
@@ -276,6 +277,14 @@ bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type
return true;
}
/****************************************************************************************
*
*/
static void led_suspend(void) {
led_off(LED_GREEN);
led_off(LED_RED);
}
/****************************************************************************************
*
*/
@@ -283,7 +292,8 @@ void set_led_gpio(int gpio, char *value) {
struct led_config_s *config;
if (strcasestr(value, "green")) config = &green;
else config = &red;
else if (strcasestr(value, "red"))config = &red;
else return;
config->gpio = gpio;
char *p = value;
@@ -325,6 +335,9 @@ void led_svc_init(void) {
led_config(LED_GREEN, green.gpio, green.color, green.bright, green.type);
led_config(LED_RED, red.gpio, red.color, red.bright, red.type);
// make sure we switch off all leds (useful for gpio expanders)
services_sleep_setsuspend(led_suspend);
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,

View File

@@ -22,7 +22,7 @@ extern bool jack_inserted_svc(void);
extern void (*spkfault_handler_svc)(bool inserted);
extern bool spkfault_svc(void);
extern void (*battery_handler_svc)(float value);
extern void (*battery_handler_svc)(float value, int cells);
extern float battery_value_svc(void);
extern uint16_t battery_level_svc(void);

View File

@@ -7,7 +7,11 @@
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/i2c.h"
@@ -20,6 +24,8 @@
#include "globdefs.h"
#include "accessors.h"
#include "messaging.h"
#include "buttons.h"
#include "services.h"
extern void battery_svc_init(void);
extern void monitor_svc_init(void);
@@ -29,24 +35,38 @@ 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 = {
int rmt_system_base_tx_channel = RMT_CHANNEL_0;
int rmt_system_base_rx_channel = RMT_CHANNEL_MAX-1;
pwm_system_t pwm_system = {
.timer = LEDC_TIMER_0,
.base_channel = LEDC_CHANNEL_0,
.max = (1 << LEDC_TIMER_13_BIT),
};
};
static EXT_RAM_ATTR struct {
uint64_t wake_gpio, wake_level;
uint64_t rtc_gpio, rtc_level;
uint32_t delay, spurious;
float battery_level;
int battery_count;
void (*idle_chain)(uint32_t now);
void (*battery_chain)(float level, int cells);
void (*suspend[10])(void);
uint32_t (*sleeper[10])(void);
} sleep_context;
static const char *TAG = "services";
/****************************************************************************************
*
*
*/
void set_chip_power_gpio(int gpio, char *value) {
bool parsed = true;
// we only parse on-chip GPIOs
if (gpio >= GPIO_NUM_MAX) return;
if (!strcasecmp(value, "vcc") ) {
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
@@ -56,16 +76,19 @@ void set_chip_power_gpio(int gpio, char *value) {
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
gpio_set_level(gpio, 0);
} else parsed = false;
if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);
}
if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);
}
/****************************************************************************************
*
*/
void set_exp_power_gpio(int gpio, char *value) {
bool parsed = true;
// we only parse on-chip GPIOs
if (gpio < GPIO_NUM_MAX) return;
if (!strcasecmp(value, "vcc") ) {
gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL);
gpio_exp_set_level(gpio, 1, true, NULL);
@@ -73,18 +96,264 @@ void set_exp_power_gpio(int gpio, char *value) {
gpio_exp_set_direction(gpio, GPIO_MODE_OUTPUT, NULL);
gpio_exp_set_level(gpio, 0, true, NULL);
} else parsed = false;
if (parsed) ESP_LOGI(TAG, "set expanded GPIO %u to %s", gpio, value);
}
}
/****************************************************************************************
*
*
*/
static void sleep_gpio_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
if (event == BUTTON_PRESSED) services_sleep_activate(SLEEP_ONGPIO);
}
/****************************************************************************************
*
*/
static void sleep_timer(uint32_t now) {
static EXT_RAM_ATTR uint32_t last, first;
// first chain the calls to pseudo_idle function
if (sleep_context.idle_chain) sleep_context.idle_chain(now);
// we need boot time for spurious timeout calculation
if (!first) first = now;
// only query callbacks every 30s if we have at least one sleeper
if (!*sleep_context.sleeper || now < last + 30*1000) return;
last = now;
// time to evaluate if we had spurious wake-up
if (sleep_context.spurious && now > sleep_context.spurious + first) {
bool spurious = true;
// see if at least one sleeper has been awake since we started
for (uint32_t (**sleeper)(void) = sleep_context.sleeper; *sleeper && spurious; sleeper++) {
spurious &= (*sleeper)() >= now - first;
}
// no activity since we woke-up, this was a spurious one
if (spurious) {
ESP_LOGI(TAG, "spurious wake of %d sec, going back to sleep", (now - first) / 1000);
services_sleep_activate(SLEEP_ONTIMER);
}
// resume normal work but we might have no "regular" inactivity delay
sleep_context.spurious = 0;
if (!sleep_context.delay) *sleep_context.sleeper = NULL;
ESP_LOGI(TAG, "wake-up was not spurious after %d sec", (now - first) / 1000);
}
// we might be here because we are waiting for spurious
if (sleep_context.delay) {
// call all sleepers to know how long for how long they have been inactive
for (uint32_t (**sleeper)(void) = sleep_context.sleeper; sleep_context.delay && *sleeper; sleeper++) {
if ((*sleeper)() < sleep_context.delay) return;
}
// if we are here, we are ready to sleep;
services_sleep_activate(SLEEP_ONTIMER);
}
}
/****************************************************************************************
*
*/
static void sleep_battery(float level, int cells) {
// chain if any
if (sleep_context.battery_chain) sleep_context.battery_chain(level, cells);
// then assess if we have to stop because of low batt
if (level < sleep_context.battery_level) {
if (sleep_context.battery_count++ == 2) services_sleep_activate(SLEEP_ONBATTERY);
} else {
sleep_context.battery_count = 0;
}
}
/****************************************************************************************
*
*/
void services_sleep_init(void) {
char *config = config_alloc_get(NVS_TYPE_STR, "sleep_config");
char *p;
// get the wake criteria
if ((p = strcasestr(config, "wake"))) {
char list[32] = "", item[8];
sscanf(p, "%*[^=]=%31[^,]", list);
p = list - 1;
while (p++ && sscanf(p, "%7[^|]", item)) {
int level = 0, gpio = atoi(item);
if (!rtc_gpio_is_valid_gpio(gpio)) {
ESP_LOGE(TAG, "invalid wake GPIO %d (not in RTC domain)", gpio);
} else {
sleep_context.wake_gpio |= 1LL << gpio;
}
if (sscanf(item, "%*[^:]:%d", &level)) sleep_context.wake_level |= level << gpio;
p = strchr(p, '|');
}
// when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can be done
if (sleep_context.wake_gpio) {
ESP_LOGI(TAG, "Sleep wake-up gpio bitmap 0x%llx (active 0x%llx)", sleep_context.wake_gpio, sleep_context.wake_level);
}
}
// do we want battery safety
PARSE_PARAM_FLOAT(config, "batt", '=', sleep_context.battery_level);
if (sleep_context.battery_level != 0.0) {
sleep_context.battery_chain = battery_handler_svc;
battery_handler_svc = sleep_battery;
ESP_LOGI(TAG, "Sleep on battery level of %.2f", sleep_context.battery_level);
}
// get the rtc-pull criteria
if ((p = strcasestr(config, "rtc"))) {
char list[32] = "", item[8];
sscanf(p, "%*[^=]=%31[^,]", list);
p = list - 1;
while (p++ && sscanf(p, "%7[^|]", item)) {
int level = 0, gpio = atoi(item);
if (!rtc_gpio_is_valid_gpio(gpio)) {
ESP_LOGE(TAG, "invalid rtc GPIO %d", gpio);
} else {
sleep_context.rtc_gpio |= 1LL << gpio;
}
if (sscanf(item, "%*[^:]:%d", &level)) sleep_context.rtc_level |= level << gpio;
p = strchr(p, '|');
}
// when moving to esp-idf more recent than 4.4.x, multiple gpio wake-up with level specific can be done
if (sleep_context.rtc_gpio) {
ESP_LOGI(TAG, "RTC forced gpio bitmap 0x%llx (active 0x%llx)", sleep_context.rtc_gpio, sleep_context.rtc_level);
}
}
// get the GPIOs that activate sleep (we could check that we have a valid wake)
if ((p = strcasestr(config, "sleep"))) {
int gpio, level = 0;
char sleep[8] = "";
sscanf(p, "%*[^=]=%7[^,]", sleep);
gpio = atoi(sleep);
if ((p = strchr(sleep, ':')) != NULL) level = atoi(p + 1);
ESP_LOGI(TAG, "Sleep activation gpio %d (active %d)", gpio, level);
button_create(NULL, gpio, level ? BUTTON_HIGH : BUTTON_LOW, true, 0, sleep_gpio_handler, 0, -1);
}
// do we want delay sleep
PARSE_PARAM(config, "delay", '=', sleep_context.delay);
sleep_context.delay *= 60*1000;
// now check why we woke-up
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT0 || cause == ESP_SLEEP_WAKEUP_EXT1) {
ESP_LOGI(TAG, "waking-up from deep sleep with cause %d", cause);
// find the type of wake-up
uint64_t wake_gpio;
if (cause == ESP_SLEEP_WAKEUP_EXT0) wake_gpio = sleep_context.wake_gpio;
else wake_gpio = esp_sleep_get_ext1_wakeup_status();
// we might be woken up by infrared in which case we want a short sleep
if (infrared_gpio() >= 0 && ((1LL << infrared_gpio()) & wake_gpio)) {
sleep_context.spurious = 1;
PARSE_PARAM(config, "spurious", '=', sleep_context.spurious);
sleep_context.spurious *= 60*1000;
ESP_LOGI(TAG, "spurious wake-up detection during %d sec", sleep_context.spurious / 1000);
}
}
// if we have inactivity timer (user-set or because of IR wake) then active counters
if (sleep_context.delay || sleep_context.spurious) {
sleep_context.idle_chain = pseudo_idle_svc;
pseudo_idle_svc = sleep_timer;
if (sleep_context.delay) ESP_LOGI(TAG, "inactivity timer of %d minute(s)", sleep_context.delay / (60*1000));
}
}
/****************************************************************************************
*
*/
void services_sleep_activate(sleep_cause_e cause) {
// call all sleep hooks that might want to do something
for (void (**suspend)(void) = sleep_context.suspend; *suspend; suspend++) (*suspend)();
// isolate all possible GPIOs, except the wake-up and RTC-maintaines ones
esp_sleep_config_gpio_isolate();
// keep RTC domain up if we need to maintain pull-up/down of some GPIO from RTC
if (sleep_context.rtc_gpio) esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
for (int i = 0; i < GPIO_NUM_MAX; i++) {
// must be a RTC GPIO
if (!rtc_gpio_is_valid_gpio(i)) continue;
// do we need to maintain a pull-up or down of that GPIO
if ((1LL << i) & sleep_context.rtc_gpio) {
if ((sleep_context.rtc_level >> i) & 0x01) rtc_gpio_pullup_en(i);
else rtc_gpio_pulldown_en(i);
// or is this not wake-up GPIO, just isolate it
} else if (!((1LL << i) & sleep_context.wake_gpio)) {
rtc_gpio_isolate(i);
}
}
// is there just one GPIO
if (sleep_context.wake_gpio & (sleep_context.wake_gpio - 1)) {
ESP_LOGI(TAG, "going to sleep cause %d, wake-up on multiple GPIO, any '1' wakes up 0x%llx", cause, sleep_context.wake_gpio);
#if defined(CONFIG_IDF_TARGET_ESP32S3) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
if (!sleep_context.wake_level) esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_LOW);
else
#endif
esp_sleep_enable_ext1_wakeup(sleep_context.wake_gpio, ESP_EXT1_WAKEUP_ANY_HIGH);
} else if (sleep_context.wake_gpio) {
int gpio = __builtin_ctzll(sleep_context.wake_gpio);
int level = (sleep_context.wake_level >> gpio) & 0x01;
ESP_LOGI(TAG, "going to sleep cause %d, wake-up on GPIO %d level %d", cause, gpio, level);
esp_sleep_enable_ext0_wakeup(gpio, level);
} else {
ESP_LOGW(TAG, "going to sleep cause %d, no wake-up option", cause);
}
// we need to use a timer in case the same button is used for sleep and wake-up and it's "pressed" vs "released" selected
if (cause == SLEEP_ONKEY) xTimerStart(xTimerCreate("sleepTimer", pdMS_TO_TICKS(1000), pdFALSE, NULL, (void (*)(void*)) esp_deep_sleep_start), 0);
else esp_deep_sleep_start();
}
/****************************************************************************************
*
*/
static void register_method(void **store, size_t size, void *method) {
for (int i = 0; i < size; i++, *store++) if (!*store) {
*store = method;
return;
}
}
/****************************************************************************************
*
*/
void services_sleep_setsuspend(void (*hook)(void)) {
register_method((void**) sleep_context.suspend, sizeof(sleep_context.suspend)/sizeof(*sleep_context.suspend), (void*) hook);
}
/****************************************************************************************
*
*/
void services_sleep_setsleeper(uint32_t (*sleeper)(void)) {
register_method((void**) sleep_context.sleeper, sizeof(sleep_context.sleeper)/sizeof(*sleep_context.sleeper), (void*) sleeper);
}
/****************************************************************************************
*
*/
void services_init(void) {
messaging_service_init();
gpio_install_isr_service(0);
#ifdef CONFIG_I2C_LOCKED
if (i2c_system_port == 0) {
i2c_system_port = 1;
@@ -95,7 +364,7 @@ void services_init(void) {
// set potential power GPIO on chip first in case expanders are power using these
parse_set_GPIO(set_chip_power_gpio);
// shared I2C bus
// shared I2C bus
const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
ESP_LOGI(TAG,"Configuring I2C sda:%d scl:%d port:%u speed:%u", i2c_config->sda_io_num, i2c_config->scl_io_num, i2c_system_port, i2c_config->master.clk_speed);
@@ -105,11 +374,11 @@ void services_init(void) {
} else {
i2c_system_port = -1;
ESP_LOGW(TAG, "no I2C configured");
}
}
const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host);
ESP_LOGI(TAG,"Configuring SPI mosi:%d miso:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->miso_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
if (spi_config->mosi_io_num != -1 && spi_config->sclk_io_num != -1) {
spi_bus_initialize( spi_system_host, spi_config, 1 );
if (spi_system_dc_gpio != -1) {
@@ -118,31 +387,31 @@ void services_init(void) {
gpio_set_level( spi_system_dc_gpio, 0 );
} else {
ESP_LOGW(TAG, "No DC GPIO set, SPI display will not work");
}
}
} else {
spi_system_host = -1;
ESP_LOGW(TAG, "no SPI configured");
}
}
// create GPIO expanders
const gpio_exp_config_t* gpio_exp_config;
for (int count = 0; (gpio_exp_config = config_gpio_exp_get(count)); count++) gpio_exp_create(gpio_exp_config);
// now set potential power GPIO on expander
// now set potential power GPIO on expander
parse_set_GPIO(set_exp_power_gpio);
// system-wide PWM timer configuration
ledc_timer_config_t pwm_timer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.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
.speed_mode = LEDC_HIGH_SPEED_MODE,
#endif
.timer_num = pwm_system.timer,
};
ledc_timer_config(&pwm_timer);
led_svc_init();

View File

@@ -0,0 +1,17 @@
/*
* Squeezelite for esp32
*
* (c) Philippe G. 2019, philippe_44@outlook.com
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
*/
#pragma once
typedef enum { SLEEP_ONTIMER, SLEEP_ONKEY, SLEEP_ONGPIO, SLEEP_ONIR, SLEEP_ONBATTERY } sleep_cause_e;
void services_sleep_activate(sleep_cause_e cause);
void services_sleep_setsuspend(void (*hook)(void));
void services_sleep_setsleeper(uint32_t (*sleeper)(void));
void services_sleep_init(void);

View File

@@ -30,10 +30,16 @@
#include "cspot_private.h"
#include "cspot_sink.h"
#include "platform_config.h"
#include "nvs_utilities.h"
#include "tools.h"
static class cspotPlayer *player;
static const struct {
const char *ns;
const char *credentials;
} spotify_ns = { .ns = "spotify", .credentials = "credentials" };
/****************************************************************************************
* Player's main class & task
*/
@@ -42,7 +48,11 @@ class cspotPlayer : public bell::Task {
private:
std::string name;
bell::WrappedSemaphore clientConnected;
std::atomic<bool> isPaused, isConnected;
std::atomic<bool> isPaused;
enum states { ABORT, LINKED, DISCO };
std::atomic<states> state;
std::string credentials;
bool zeroConf;
int startOffset, volume = 0, bitrate = 160;
httpd_handle_t serverHandle;
@@ -57,6 +67,7 @@ private:
void eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event);
void trackHandler(void);
size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId);
void enableZeroConf(void);
void runTask();
@@ -79,8 +90,25 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo
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, "deviceName") ) != NULL) this->name = item->valuestring;
else this->name = name;
cJSON_Delete(config);
else this->name = name;
if ((item = cJSON_GetObjectItem(config, "zeroConf")) != NULL) {
zeroConf = item->valueint;
cJSON_Delete(config);
} else {
zeroConf = true;
cJSON_AddNumberToObject(config, "zeroConf", 1);
config_set_cjson_str_and_free("cspot_config", config);
}
// get optional credentials from own NVS
if (!zeroConf) {
char *credentials = (char*) get_nvs_value_alloc_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, NULL);
if (credentials) {
this->credentials = credentials;
free(credentials);
}
}
if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160;
}
@@ -92,8 +120,7 @@ size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackI
trackHandler();
}
dataHandler(pcm, bytes);
return bytes;
return dataHandler(pcm, bytes);
}
extern "C" {
@@ -208,7 +235,7 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
}
case cspot::SpircHandler::EventType::DISC:
cmdHandler(CSPOT_DISC);
isConnected = false;
state = DISCO;
break;
case cspot::SpircHandler::EventType::SEEK: {
cmdHandler(CSPOT_SEEK, std::get<int>(event->data));
@@ -266,7 +293,7 @@ void cspotPlayer::command(cspot_event_t event) {
* generate any cspot::event */
case CSPOT_DISC:
cmdHandler(CSPOT_DISC);
isConnected = false;
state = ABORT;
break;
// spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler
case CSPOT_VOLUME_UP:
@@ -286,34 +313,48 @@ void cspotPlayer::command(cspot_event_t event) {
}
}
void cspotPlayer::runTask() {
void cspotPlayer::enableZeroConf(void) {
httpd_uri_t request = {
.uri = "/spotify_info",
.method = HTTP_GET,
.handler = ::handleGET,
.user_ctx = NULL,
};
};
// register GET and POST handler for built-in server
httpd_register_uri_handler(serverHandle, &request);
request.method = HTTP_POST;
request.handler = ::handlePOST;
httpd_register_uri_handler(serverHandle, &request);
// construct blob for that player
blob = std::make_unique<cspot::LoginBlob>(name);
CSPOT_LOG(info, "ZeroConf mode (port %d)", serverPort);
// 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"} });
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
}
void cspotPlayer::runTask() {
bool useZeroConf = zeroConf;
// construct blob for that player
blob = std::make_unique<cspot::LoginBlob>(name);
CSPOT_LOG(info, "CSpot instance service name %s (id %s)", blob->getDeviceName().c_str(), blob->getDeviceId().c_str());
if (!zeroConf && !credentials.empty()) {
blob->loadJson(credentials);
CSPOT_LOG(info, "Reusable credentials mode");
} else {
// whether we want it or not we must use ZeroConf
useZeroConf = true;
enableZeroConf();
}
// gone with the wind...
while (1) {
clientConnected.wait();
CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
if (useZeroConf) clientConnected.wait();
CSPOT_LOG(info, "Spotify client launched for %s", name.c_str());
auto ctx = cspot::Context::createFromBlob(blob);
@@ -322,12 +363,26 @@ void cspotPlayer::runTask() {
else ctx->config.audioFormat = AudioFormat_OGG_VORBIS_160;
ctx->session->connectWithRandomAp();
auto token = ctx->session->authenticate(blob);
ctx->config.authData = ctx->session->authenticate(blob);
// Auth successful
if (token.size() > 0) {
if (ctx->config.authData.size() > 0) {
// we might have been forced to use zeroConf, so store credentials and reset zeroConf usage
if (!zeroConf) {
useZeroConf = false;
// can't call store_nvs... from a task running on EXTRAM stack
TimerHandle_t timer = xTimerCreate( "credentials", 1, pdFALSE, strdup(ctx->getCredentialsJson().c_str()),
[](TimerHandle_t xTimer) {
auto credentials = (char*) pvTimerGetTimerID(xTimer);
store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, credentials, 0);
free(credentials);
xTimerDelete(xTimer, portMAX_DELAY);
} );
xTimerStart(timer, portMAX_DELAY);
}
spirc = std::make_unique<cspot::SpircHandler>(ctx);
isConnected = true;
state = LINKED;
// set call back to calculate a hash on trackId
spirc->getTrackPlayer()->setDataCallback(
@@ -348,7 +403,7 @@ void cspotPlayer::runTask() {
cmdHandler(CSPOT_VOLUME, volume);
// exit when player has stopped (received a DISC)
while (isConnected) {
while (state == LINKED) {
ctx->session->handlePacket();
// low-accuracy polling events
@@ -372,23 +427,32 @@ void cspotPlayer::runTask() {
spirc->setPause(true);
}
}
// on disconnect, stay in the core loop unless we are in ZeroConf mode
if (state == DISCO) {
// update volume then
cJSON *config = config_alloc_get_cjson("cspot_config");
cJSON_DeleteItemFromObject(config, "volume");
cJSON_AddNumberToObject(config, "volume", volume);
config_set_cjson_str_and_free("cspot_config", config);
// in ZeroConf mod, stay connected (in this loop)
if (!zeroConf) state = LINKED;
}
}
spirc->disconnect();
spirc.reset();
CSPOT_LOG(info, "disconnecting player %s", name.c_str());
} else {
CSPOT_LOG(error, "failed authentication, forcing ZeroConf");
if (!useZeroConf) enableZeroConf();
useZeroConf = true;
}
// we want to release memory ASAP and for sure
ctx.reset();
token.clear();
// update volume when we disconnect
cJSON *config = config_alloc_get_cjson("cspot_config");
cJSON_DeleteItemFromObject(config, "volume");
cJSON_AddNumberToObject(config, "volume", volume);
config_set_cjson_str_and_free("cspot_config", config);
ctx.reset();
}
}

View File

@@ -1,7 +1,9 @@
file(GLOB AACDEC_SOURCES "src/*.c")
file(GLOB AACDEC_HEADERS "src/*.h" "oscl/*.h" "include/*.h")
add_library(opencore-aacdec SHARED ${AACDEC_SOURCES})
add_library(opencore-aacdec STATIC ${AACDEC_SOURCES})
if(NOT MSVC)
target_compile_options(opencore-aacdec PRIVATE -Wno-array-parameter)
endif()
add_definitions(-DAAC_PLUS -DHQ_SBR -DPARAMETRICSTEREO -DC_EQUIVALENT)
target_compile_options(opencore-aacdec PRIVATE -Wno-array-parameter)
target_include_directories(opencore-aacdec PUBLIC "src/" "oscl/" "include/")

View File

@@ -1,8 +1,8 @@
#include "AACDecoder.h"
#include <assert.h>
#include <stdlib.h> // for free, malloc
#include <string.h>
#include <assert.h>
#include "e_tmp4audioobjecttype.h"
#include "pvmp4audiodecoder_api.h"

View File

@@ -24,7 +24,7 @@ class DecodersInstance {
void ensureAAC() {
// if (aacDecoder == NULL) {
// aacDecoder = AACInitDecoder();
// aacDecoder = AACInitDecoder();
// }
}

View File

@@ -7,7 +7,8 @@
using namespace bell;
MP3Container::MP3Container(std::istream& istr, const std::byte* headingBytes) : bell::AudioContainer(istr) {
MP3Container::MP3Container(std::istream& istr, const std::byte* headingBytes)
: bell::AudioContainer(istr) {
if (headingBytes != nullptr) {
memcpy(buffer.data(), headingBytes, 7);
bytesInBuffer = 7;
@@ -38,7 +39,6 @@ std::byte* MP3Container::readSample(uint32_t& len) {
bytesInBuffer -= toConsume;
}
if (!this->fillBuffer()) {
len = 0;
return nullptr;

View File

@@ -292,7 +292,7 @@ void reader::extract_all_files(std::string dest_directory) {
if (fileType == '0' && !fileName.starts_with("._")) {
#else
if (fileType == '0' && fileName.find("._") != 0) {
#endif
#endif
std::string path = dest_directory + "/" + fileName;
size_t pos = 0;

View File

@@ -2,76 +2,77 @@
#include "MGStreamAdapter.h"
mg_buf::mg_buf(struct mg_connection* _conn) : conn(_conn) {
setp(buffer, buffer + BUF_SIZE - 1); // -1 to leave space for overflow '\0'
setp(buffer, buffer + BUF_SIZE - 1); // -1 to leave space for overflow '\0'
}
mg_buf::int_type mg_buf::overflow(int_type c) {
if (c != EOF) {
*pptr() = c;
pbump(1);
}
if (c != EOF) {
*pptr() = c;
pbump(1);
}
if (flush_buffer() == EOF) {
return EOF;
}
if (flush_buffer() == EOF) {
return EOF;
}
return c;
return c;
}
int mg_buf::flush_buffer() {
int len = int(pptr() - pbase());
if (mg_write(conn, buffer, len) != len) {
return EOF;
}
pbump(-len); // reset put pointer accordingly
return len;
int len = int(pptr() - pbase());
if (mg_write(conn, buffer, len) != len) {
return EOF;
}
pbump(-len); // reset put pointer accordingly
return len;
}
int mg_buf::sync() {
if (flush_buffer() == EOF) {
return -1; // return -1 on error
}
return 0;
if (flush_buffer() == EOF) {
return -1; // return -1 on error
}
return 0;
}
MGStreamAdapter::MGStreamAdapter(struct mg_connection* _conn) : std::ostream(&buf), buf(_conn) {
rdbuf(&buf); // set the custom streambuf
MGStreamAdapter::MGStreamAdapter(struct mg_connection* _conn)
: std::ostream(&buf), buf(_conn) {
rdbuf(&buf); // set the custom streambuf
}
mg_read_buf::mg_read_buf(struct mg_connection* _conn) : conn(_conn) {
setg(buffer + BUF_SIZE, // beginning of putback area
buffer + BUF_SIZE, // read position
buffer + BUF_SIZE); // end position
setg(buffer + BUF_SIZE, // beginning of putback area
buffer + BUF_SIZE, // read position
buffer + BUF_SIZE); // end position
}
mg_read_buf::int_type mg_read_buf::underflow() {
if (gptr() < egptr()) { // buffer not exhausted
return traits_type::to_int_type(*gptr());
}
char* base = buffer;
char* start = base;
if (eback() == base) { // true when this isn't the first fill
// Make arrangements for putback characters
std::memmove(base, egptr() - 2, 2);
start += 2;
}
// Read new characters
int n = mg_read(conn, start, buffer + BUF_SIZE - start);
if (n == 0) {
return traits_type::eof();
}
// Set buffer pointers
setg(base, start, start + n);
// Return next character
if (gptr() < egptr()) { // buffer not exhausted
return traits_type::to_int_type(*gptr());
}
char* base = buffer;
char* start = base;
if (eback() == base) { // true when this isn't the first fill
// Make arrangements for putback characters
std::memmove(base, egptr() - 2, 2);
start += 2;
}
// Read new characters
int n = mg_read(conn, start, buffer + BUF_SIZE - start);
if (n == 0) {
return traits_type::eof();
}
// Set buffer pointers
setg(base, start, start + n);
// Return next character
return traits_type::to_int_type(*gptr());
}
MGInputStreamAdapter::MGInputStreamAdapter(struct mg_connection* _conn) : std::istream(&buf), buf(_conn) {
rdbuf(&buf); // set the custom streambuf
MGInputStreamAdapter::MGInputStreamAdapter(struct mg_connection* _conn)
: std::istream(&buf), buf(_conn) {
rdbuf(&buf); // set the custom streambuf
}

View File

@@ -1,12 +1,12 @@
#include "X509Bundle.h"
#include <mbedtls/md.h> // for mbedtls_md, mbedtls_md_get_size
#include <mbedtls/pk.h> // for mbedtls_pk_can_do, mbedtls_pk_pa...
#include <mbedtls/ssl.h> // for mbedtls_ssl_conf_ca_chain, mbedt...
#include <mbedtls/x509.h> // for mbedtls_x509_buf, MBEDTLS_ERR_X5...
#include <stdlib.h> // for free, calloc
#include <string.h> // for memcmp, memcpy
#include <stdexcept> // for runtime_error
#include <mbedtls/md.h> // for mbedtls_md, mbedtls_md_get_size
#include <mbedtls/pk.h> // for mbedtls_pk_can_do, mbedtls_pk_pa...
#include <mbedtls/ssl.h> // for mbedtls_ssl_conf_ca_chain, mbedt...
#include <mbedtls/x509.h> // for mbedtls_x509_buf, MBEDTLS_ERR_X5...
#include <stdlib.h> // for free, calloc
#include <string.h> // for memcmp, memcpy
#include <stdexcept> // for runtime_error
#include "BellLogger.h" // for AbstractLogger, BELL_LOG

View File

@@ -1,8 +1,8 @@
#pragma once
#include <cstring>
#include <iostream>
#include <ostream>
#include <cstring>
#include "civetweb.h"
const size_t BUF_SIZE = 1024;
@@ -36,25 +36,24 @@ class MGStreamAdapter : public std::ostream {
// Custom streambuf
class mg_read_buf : public std::streambuf {
private:
struct mg_connection* conn;
char buffer[BUF_SIZE];
private:
struct mg_connection* conn;
char buffer[BUF_SIZE];
public:
mg_read_buf(struct mg_connection* _conn);
protected:
virtual int_type underflow();
public:
mg_read_buf(struct mg_connection* _conn);
protected:
virtual int_type underflow();
};
/**
* @brief Adapts istream to mg_read
*/
class MGInputStreamAdapter : public std::istream {
private:
mg_read_buf buf;
private:
mg_read_buf buf;
public:
MGInputStreamAdapter(struct mg_connection* _conn);
public:
MGInputStreamAdapter(struct mg_connection* _conn);
};

View File

@@ -141,14 +141,14 @@ std::vector<uint8_t> CryptoMbedTLS::pbkdf2HmacSha1(
mbedtls_pkcs5_pbkdf2_hmac(&sha1Context, password.data(), password.size(),
salt.data(), salt.size(), iterations, digestSize,
digest.data());
// Free sha context
mbedtls_md_free(&sha1Context);
#else
mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1, password.data(), password.size(),
salt.data(), salt.size(), iterations, digestSize,
digest.data());
#endif
mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1, password.data(),
password.size(), salt.data(), salt.size(),
iterations, digestSize, digest.data());
#endif
return digest;
}

View File

@@ -31,8 +31,8 @@ class CryptoMbedTLS {
CryptoMbedTLS();
~CryptoMbedTLS();
// Base64
std::vector<uint8_t> base64Decode(const std::string& data);
std::string base64Encode(const std::vector<uint8_t>& data);
static std::vector<uint8_t> base64Decode(const std::string& data);
static std::string base64Encode(const std::vector<uint8_t>& data);
// Sha1
void sha1Init();

View File

@@ -3,10 +3,19 @@
#include <stdint.h>
#include <memory>
#include "Crypto.h"
#include "LoginBlob.h"
#include "MercurySession.h"
#include "TimeProvider.h"
#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE...
#include "protobuf/metadata.pb.h"
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
#include "nlohmann/detail/json_pointer.hpp" // for json_pointer<>::string_t
#include "nlohmann/json.hpp" // for basic_json<>::object_t, basic_json
#include "nlohmann/json_fwd.hpp" // for json
#endif
namespace cspot {
struct Context {
@@ -26,6 +35,32 @@ struct Context {
std::shared_ptr<TimeProvider> timeProvider;
std::shared_ptr<cspot::MercurySession> session;
std::string getCredentialsJson() {
#ifdef BELL_ONLY_CJSON
cJSON* json_obj = cJSON_CreateObject();
cJSON_AddStringToObject(json_obj, "authData",
Crypto::base64Encode(config.authData).c_str());
cJSON_AddNumberToObject(
json_obj, "authType",
AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS);
cJSON_AddStringToObject(json_obj, "username", config.username.c_str());
char* str = cJSON_PrintUnformatted(json_obj);
cJSON_Delete(json_obj);
std::string json_objStr(str);
free(str);
return json_objStr;
#else
nlohmann::json obj;
obj["authData"] = Crypto::base64Encode(config.authData);
obj["authType"] =
AuthenticationType_AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS;
obj["username"] = config.username;
return obj.dump();
#endif
}
static std::shared_ptr<Context> createFromBlob(
std::shared_ptr<LoginBlob> blob) {

View File

@@ -88,8 +88,8 @@ class MercurySession : public bell::Task, public cspot::Session {
void unregisterAudioKey(uint32_t sequenceId);
uint32_t requestAudioKey(const std::vector<uint8_t>& trackId,
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback);
const std::vector<uint8_t>& fileId,
AudioKeyCallback audioCallback);
std::string getCountryCode();

View File

@@ -7,7 +7,7 @@
#include <variant> // for variant
#include <vector> // for vector
#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track...
#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track...
#include "TrackQueue.h"
#include "protobuf/spirc.pb.h" // for MessageType

View File

@@ -33,7 +33,8 @@ class TrackPlayer : bell::Task {
public:
// Callback types
typedef std::function<void(std::shared_ptr<QueuedTrack>)> TrackLoadedCallback;
typedef std::function<size_t(uint8_t*, size_t, std::string_view)> DataCallback;
typedef std::function<size_t(uint8_t*, size_t, std::string_view)>
DataCallback;
typedef std::function<void()> EOFCallback;
typedef std::function<bool()> isAiringCallback;

View File

@@ -3,8 +3,8 @@
#include <stddef.h> // for size_t
#include <atomic>
#include <deque>
#include <mutex>
#include <functional>
#include <mutex>
#include "BellTask.h"
#include "PlaybackState.h"
@@ -94,7 +94,6 @@ class TrackQueue : public bell::Task {
std::shared_ptr<bell::WrappedSemaphore> playableSemaphore;
std::atomic<bool> notifyPending = false;
void runTask() override;
void stopTask();

View File

@@ -1,9 +1,9 @@
#pragma once
#include <pb_encode.h>
#include <optional>
#include <string_view>
#include <vector>
#include <optional>
#include "NanoPBHelper.h"
#include "pb_decode.h"
#include "protobuf/spirc.pb.h"

View File

@@ -2,4 +2,9 @@ LoginCredentials.username max_size:30, fixed_length:false
LoginCredentials.auth_data max_size:512, fixed_length:false
SystemInfo.system_information_string max_size:16, fixed_length:false
SystemInfo.device_id max_size:50, fixed_length:false
ClientResponseEncrypted.version_string max_size:32, fixed_length:false
ClientResponseEncrypted.version_string max_size:32, fixed_length:false
APWelcome.canonical_username max_size:30, fixed_length:false
APWelcome.reusable_auth_credentials max_size:512, fixed_length:false
APWelcome.lfs_secret max_size:128, fixed_length:false
AccountInfoFacebook.access_token max_size:128, fixed_length:false
AccountInfoFacebook.machine_id max_size:50, fixed_length:false

View File

@@ -37,6 +37,11 @@ enum Os {
OS_BCO = 0x16;
}
enum AccountType {
Spotify = 0x0;
Facebook = 0x1;
}
enum AuthenticationType {
AUTHENTICATION_USER_PASS = 0x0;
AUTHENTICATION_STORED_SPOTIFY_CREDENTIALS = 0x1;
@@ -62,4 +67,28 @@ message ClientResponseEncrypted {
required LoginCredentials login_credentials = 0xa;
required SystemInfo system_info = 0x32;
optional string version_string = 0x46;
}
message APWelcome {
required string canonical_username = 0xa;
required AccountType account_type_logged_in = 0x14;
required AccountType credentials_type_logged_in = 0x19;
required AuthenticationType reusable_auth_credentials_type = 0x1e;
required bytes reusable_auth_credentials = 0x28;
optional bytes lfs_secret = 0x32;
optional AccountInfo account_info = 0x3c;
optional AccountInfoFacebook fb = 0x46;
}
message AccountInfo {
optional AccountInfoSpotify spotify = 0x1;
optional AccountInfoFacebook facebook = 0x2;
}
message AccountInfoSpotify {
}
message AccountInfoFacebook {
optional string access_token = 0x1;
optional string machine_id = 0x2;
}

View File

@@ -6,7 +6,7 @@
#include <string_view> // for string_view
#include <vector> // for vector
#include "HTTPClient.h" // for HTTPClient, HTTPClient::Response
#include "HTTPClient.h" // for HTTPClient, HTTPClient::Response
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else

View File

@@ -10,11 +10,11 @@
#include "AccessKeyFetcher.h" // for AccessKeyFetcher
#include "BellLogger.h" // for AbstractLogger
#include "Crypto.h"
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "SocketStream.h" // for SocketStream
#include "Utils.h" // for bigNumAdd, bytesToHexString, string...
#include "WrappedSemaphore.h" // for WrappedSemaphore
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "SocketStream.h" // for SocketStream
#include "Utils.h" // for bigNumAdd, bytesToHexString, string...
#include "WrappedSemaphore.h" // for WrappedSemaphore
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else

View File

@@ -3,10 +3,10 @@
#include <stdio.h> // for sprintf
#include <initializer_list> // for initializer_list
#include "BellLogger.h" // for AbstractLogger
#include "ConstantParameters.h" // for brandName, cspot, protoc...
#include "Logger.h" // for CSPOT_LOG
#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE...
#include "BellLogger.h" // for AbstractLogger
#include "ConstantParameters.h" // for brandName, cspot, protoc...
#include "Logger.h" // for CSPOT_LOG
#include "protobuf/authentication.pb.h" // for AuthenticationType_AUTHE...
#ifdef BELL_ONLY_CJSON
#include "cJSON.h"
#else
@@ -144,6 +144,7 @@ void LoginBlob::loadJson(const std::string& json) {
this->username = cJSON_GetObjectItem(root, "username")->valuestring;
std::string authDataObject =
cJSON_GetObjectItem(root, "authData")->valuestring;
this->authData = crypto->base64Decode(authDataObject);
cJSON_Delete(root);
#else
auto root = nlohmann::json::parse(json);

View File

@@ -7,7 +7,7 @@
#include <type_traits> // for remove_extent_t, __underlying_type_impl<>:...
#include <utility> // for pair
#ifndef _WIN32
#include <arpa/inet.h> // for htons, ntohs, htonl, ntohl
#include <arpa/inet.h> // for htons, ntohs, htonl, ntohl
#endif
#include "BellLogger.h" // for AbstractLogger
#include "BellTask.h" // for Task

View File

@@ -1,22 +1,22 @@
#include "PlainConnection.h"
#ifndef _WIN32
#include <netdb.h> // for addrinfo, freeaddrinfo, getaddrinfo
#include <netinet/in.h> // for IPPROTO_IP, IPPROTO_TCP
#include <sys/errno.h> // for EAGAIN, EINTR, ETIMEDOUT, errno
#include <sys/socket.h> // for setsockopt, connect, recv, send, shutdown
#include <sys/time.h> // for timeval
#include <cstring> // for memset
#include <stdexcept> // for runtime_error
#include <netinet/tcp.h> // for TCP_NODELAY
#include <netdb.h> // for addrinfo, freeaddrinfo, getaddrinfo
#include <netdb.h>
#include <netinet/in.h> // for IPPROTO_IP, IPPROTO_TCP
#include <netinet/tcp.h> // for TCP_NODELAY
#include <sys/errno.h> // for EAGAIN, EINTR, ETIMEDOUT, errno
#include <sys/socket.h> // for setsockopt, connect, recv, send, shutdown
#include <sys/time.h> // for timeval
#include <cstring> // for memset
#include <stdexcept> // for runtime_error
#else
#include <ws2tcpip.h>
#endif
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "Utils.h" // for extract, pack
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Packet.h" // for cspot
#include "Utils.h" // for extract, pack
using namespace cspot;

View File

@@ -17,6 +17,10 @@
#include "PlainConnection.h" // for PlainConnection, timeoutCallback
#include "ShannonConnection.h" // for ShannonConnection
#include "NanoPBHelper.h" // for pbPutString, pbEncode, pbDecode
#include "pb_decode.h"
#include "protobuf/authentication.pb.h"
using random_bytes_engine =
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, uint8_t>;
@@ -79,9 +83,12 @@ std::vector<uint8_t> Session::authenticate(std::shared_ptr<LoginBlob> blob) {
auto packet = this->shanConn->recvPacket();
switch (packet.command) {
case AUTH_SUCCESSFUL_COMMAND: {
APWelcome welcome;
CSPOT_LOG(debug, "Authorization successful");
return std::vector<uint8_t>(
{0x1}); // TODO: return actual reusable credentaials to be stored somewhere
pbDecode(welcome, APWelcome_fields, packet.data);
return std::vector<uint8_t>(welcome.reusable_auth_credentials.bytes,
welcome.reusable_auth_credentials.bytes +
welcome.reusable_auth_credentials.size);
break;
}
case AUTH_DECLINED_COMMAND: {

View File

@@ -38,7 +38,7 @@ SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx) {
this->notify();
// Send playback start event, unpause
sendEvent(EventType::PLAYBACK_START, (int) track->requestedPosition);
sendEvent(EventType::PLAYBACK_START, (int)track->requestedPosition);
sendEvent(EventType::PLAY_PAUSE, false);
};
@@ -111,7 +111,7 @@ void SpircHandler::updatePositionMs(uint32_t position) {
void SpircHandler::disconnect() {
this->trackQueue->stopTask();
this->trackPlayer->resetState();
this->trackPlayer->stop();
this->ctx->session->disconnect();
}

View File

@@ -1,8 +1,8 @@
#include "TimeProvider.h"
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Utils.h" // for extract, getCurrentTimestamp
#include "BellLogger.h" // for AbstractLogger
#include "Logger.h" // for CSPOT_LOG
#include "Utils.h" // for extract, getCurrentTimestamp
#ifndef _WIN32
#include <arpa/inet.h>
#endif

View File

@@ -13,8 +13,10 @@
#include "WrappedSemaphore.h" // for WrappedSemaphore
#ifdef BELL_VORBIS_FLOAT
#define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000))
#define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section))
#define VORBIS_SEEK(file, position) \
(ov_time_seek(file, (double)position / 1000))
#define VORBIS_READ(file, buffer, bufferSize, section) \
(ov_read(file, buffer, bufferSize, 0, 2, 1, section))
#else
#define VORBIS_SEEK(file, position) (ov_time_seek(file, position))
#define VORBIS_READ(file, buffer, bufferSize, section) \
@@ -68,6 +70,7 @@ TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx,
TrackPlayer::~TrackPlayer() {
isRunning = false;
resetState();
std::scoped_lock lock(runningMutex);
}
@@ -233,8 +236,8 @@ void TrackPlayer::runTask() {
if (!currentSongPlaying || pendingReset)
break;
written =
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier);
written = dataCallback(pcmBuffer.data() + (ret - toWrite),
toWrite, track->identifier);
}
if (written == 0) {
BELL_SLEEP_MS(50);

View File

@@ -24,11 +24,7 @@ void TrackReference::decodeURI() {
gid = bigNumAdd(gid, d);
}
#if __cplusplus >= 202002L
if (uri.starts_with("episode")) {
#else
if (uri.find("episode") == 0) {
#endif
if (uri.find("episode:") != std::string::npos) {
type = Type::EPISODE;
}
}

View File

@@ -1,12 +1,12 @@
#include "Utils.h"
#include <stdlib.h> // for strtol
#include <stdlib.h> // for strtol
#include <chrono>
#include <iomanip> // for operator<<, setfill, setw
#include <iostream> // for basic_ostream, hex
#include <sstream> // for stringstream
#include <string> // for string
#include <type_traits> // for enable_if<>::type
#include <chrono>
#ifndef _WIN32
#include <arpa/inet.h>
#endif

View File

@@ -136,10 +136,8 @@ static bool cmd_handler(cspot_event_t event, ...) {
displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1);
break;
case CSPOT_TRACK_INFO: {
uint32_t duration = va_arg(args, int);
uint32_t offset = va_arg(args, int);
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
char *artwork = va_arg(args, char*);
uint32_t duration = va_arg(args, int), offset = va_arg(args, int);
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*), *artwork = va_arg(args, char*);
if (artwork && displayer_can_artwork()) {
ESP_LOGI(TAG, "requesting artwork %s", artwork);
http_download(artwork, 128*1024, got_artwork, NULL);

View File

@@ -25,7 +25,7 @@ typedef enum { CSPOT_START, CSPOT_DISC, CSPOT_FLUSH, CSPOT_STOP, CSPOT_PLAY, CS
typedef bool (*cspot_cmd_cb_t)(cspot_event_t event, ...);
typedef bool (*cspot_cmd_vcb_t)(cspot_event_t event, va_list args);
typedef void (*cspot_data_cb_t)(const uint8_t *data, size_t len);
typedef uint32_t (*cspot_data_cb_t)(const uint8_t *data, size_t len);
/**
* @brief init sink mode (need to be provided)

View File

@@ -70,7 +70,7 @@ static void sendBUTN(int code, bool pressed) {
pkt_header.jiffies = htonl(gettime_ms());
pkt_header.button = htonl(code + (pressed ? DOWN_OFS : UP_OFS));
LOG_INFO("sending BUTN code %04x %s", code, pressed ? "down" : "up");
LOG_DEBUG("sending BUTN code %04x %s", code, pressed ? "down" : "up");
LOCK_P;
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));

View File

@@ -65,15 +65,16 @@ extern log_level loglevel;
/****************************************************************************************
* Common sink data handler
*/
static void sink_data_handler(const uint8_t *data, uint32_t len)
static uint32_t sink_data_handler(const uint8_t *data, uint32_t len, int retries)
{
size_t bytes, space;
int wait = 10;
uint32_t written = 0;
int wait = retries + 1;
// would be better to lock output, but really, it does not matter
if (!output.external) {
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
return;
return 0;
}
LOCK_O;
@@ -98,27 +99,38 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
len -= bytes;
data += bytes;
written += bytes;
// allow i2s to empty the buffer if needed
if (len && !space) {
if (output.state == OUTPUT_RUNNING) wait--;
if (!retries) break;
wait--;
UNLOCK_O; usleep(50000); LOCK_O;
}
}
if (!wait) {
// re-align the buffer according to what we throw away
// re-align the buffer according to what we threw away
_buf_inc_writep(outputbuf, outputbuf->size - (BYTES_PER_FRAME - (len % BYTES_PER_FRAME)));
LOG_WARN("Waited too long, dropping frames %d", len);
}
UNLOCK_O;
return written;
}
/****************************************************************************************
* BT sink data handler
*/
#if CONFIG_BT_SINK
static void bt_sink_data_handler(const uint8_t *data, uint32_t len) {
sink_data_handler(data, len, 10);
}
/****************************************************************************************
* BT sink command handler
*/
#if CONFIG_BT_SINK
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
@@ -195,7 +207,7 @@ static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t play
raop_sync.playtime = playtime;
raop_sync.len = len;
sink_data_handler(data, len);
sink_data_handler(data, len, 10);
}
/****************************************************************************************
@@ -332,9 +344,17 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
#endif
/****************************************************************************************
* cspot sink command handler
* cspot sink data handler
*/
#if CONFIG_CSPOT_SINK
static uint32_t cspot_sink_data_handler(const uint8_t *data, uint32_t len) {
return sink_data_handler(data, len, 0);
}
/****************************************************************************************
* cspot sink command handler
*/
static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
@@ -433,7 +453,7 @@ void register_external(void) {
enable_bt_sink = !strcmp(p,"1") || !strcasecmp(p,"y");
free(p);
if (!strcasestr(output.device, "BT") && enable_bt_sink) {
bt_sink_init(bt_sink_cmd_handler, sink_data_handler);
bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
}
}
#endif
@@ -454,7 +474,7 @@ void register_external(void) {
enable_cspot = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
free(p);
if (enable_cspot){
cspot_sink_init(cspot_cmd_handler, sink_data_handler);
cspot_sink_init(cspot_cmd_handler, cspot_sink_data_handler);
LOG_INFO("Initializing CSpot sink");
}
}

View File

@@ -9,12 +9,14 @@
*
*/
#include <assert.h>
#include "driver/gpio.h"
#include "squeezelite.h"
#include "equalizer.h"
#include "perf_trace.h"
#include "platform_config.h"
#include <assert.h>
#include "services.h"
#include "led.h"
extern struct outputstate output;
extern struct buffer *outputbuf;
@@ -39,6 +41,7 @@ static bool running = false;
static uint8_t *btout;
static frames_t oframes;
static bool stats;
static uint32_t bt_idle_since;
static int _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);
@@ -60,10 +63,28 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
RESET_MIN_MAX_DURATION(lock_out_time)
DECLARE_ALL_MIN_MAX;
/****************************************************************************************
* Get inactivity callback
*/
static uint32_t bt_idle_callback(void) {
return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - bt_idle_since : 0;
}
/****************************************************************************************
* Init BT sink
*/
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
loglevel = level;
running = true;
// idle counter
bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
services_sleep_setsleeper(bt_idle_callback);
// even BT has a right to use led :-)
led_blink(LED_GREEN, 200, 1000);
running = true;
output.write_cb = &_write_frames;
hal_bluetooth_init(device);
char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
@@ -72,6 +93,9 @@ void output_init_bt(log_level level, char *device, unsigned output_buf_size, cha
equalizer_set_samplerate(output.current_sample_rate);
}
/****************************************************************************************
* Close BT sink
*/
void output_close_bt(void) {
LOCK;
running = false;
@@ -80,6 +104,9 @@ void output_close_bt(void) {
equalizer_close();
}
/****************************************************************************************
* Data framing callback
*/
static int _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) {
@@ -120,6 +147,9 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
return (int)out_frames;
}
/****************************************************************************************
* Data callback for BT stack
*/
int32_t output_bt_data(uint8_t *data, int32_t len) {
int32_t iframes = len / BYTES_PER_FRAME, start_timer = 0;
@@ -153,6 +183,9 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
return oframes * BYTES_PER_FRAME;
}
/****************************************************************************************
* Tick for BT
*/
void output_bt_tick(void) {
static time_t lastTime=0;
@@ -186,3 +219,18 @@ void output_bt_tick(void) {
}
}
/****************************************************************************************
* BT playback stop
*/
void output_bt_stop(void) {
led_blink(LED_GREEN, 200, 1000);
bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
}
/****************************************************************************************
* BT playback start
*/
void output_bt_start(void) {
led_on(LED_GREEN);
bt_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
}

View File

@@ -41,6 +41,7 @@ sure that using rate_delay would fix that
#include "adac.h"
#include "time.h"
#include "led.h"
#include "services.h"
#include "monitor.h"
#include "platform_config.h"
#include "gpio_exp.h"
@@ -102,6 +103,8 @@ const struct adac_s *adac = &dac_external;
static log_level loglevel;
static uint32_t i2s_idle_since;
static void (*pseudo_idle_chain)(uint32_t);
static bool (*slimp_handler_chain)(u8_t *data, int len);
static bool jack_mutes_amp;
static bool running, isI2SStarted, ended;
@@ -111,7 +114,6 @@ static frames_t oframes;
static struct {
bool enabled;
u8_t *buf;
size_t count;
} spdif;
static size_t dma_buf_frames;
static TaskHandle_t output_i2s_task;
@@ -126,7 +128,8 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
static void output_thread_i2s(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 spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst);
static void (*jack_handler_chain)(bool inserted);
#define I2C_PORT 0
@@ -198,6 +201,13 @@ static void set_amp_gpio(int gpio, char *value) {
}
#endif
/****************************************************************************************
* Get inactivity callback
*/
static uint32_t i2s_idle_callback(void) {
return output.state <= OUTPUT_STOPPED ? pdTICKS_TO_MS(xTaskGetTickCount()) - i2s_idle_since : 0;
}
/****************************************************************************************
* Set pin from config string
*/
@@ -411,7 +421,19 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
else adac->speaker(true);
adac->headset(jack_inserted_svc());
// do we want stats
p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
pseudo_idle_chain = pseudo_idle_svc;
pseudo_idle_svc = i2s_stats;
}
free(p);
// register a callback for inactivity
i2s_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
services_sleep_setsleeper(i2s_idle_callback);
// create task as a FreeRTOS task but uses stack in internal RAM
{
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
@@ -419,17 +441,8 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 );
}
// do we want stats
p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
pseudo_idle_chain = pseudo_idle_svc;
pseudo_idle_svc = i2s_stats;
}
free(p);
}
/****************************************************************************************
* Terminate DAC output
*/
@@ -493,7 +506,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);
@@ -508,6 +521,7 @@ static void output_thread_i2s(void *arg) {
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
} else if (output.state == OUTPUT_STOPPED) {
i2s_idle_since = pdTICKS_TO_MS(xTaskGetTickCount());
adac->speaker(false);
led_blink(LED_GREEN, 200, 1000);
} else if (output.state == OUTPUT_RUNNING) {
@@ -526,7 +540,6 @@ static void output_thread_i2s(void *arg) {
isI2SStarted = false;
i2s_stop(CONFIG_I2S_NUM);
adac->power(ADAC_STANDBY);
spdif.count = 0;
}
usleep(100000);
continue;
@@ -573,6 +586,7 @@ static void output_thread_i2s(void *arg) {
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
i2s_start(CONFIG_I2S_NUM);
adac->power(ADAC_ON);
if (spdif.enabled) spdif_convert(NULL, 0, NULL);
}
// this does not work well as set_sample_rates resets the fifos (and it's too early)
@@ -602,7 +616,7 @@ 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);
i2s_write(CONFIG_I2S_NUM, spdif.buf, chunk * 16, &obytes, portMAX_DELAY);
bytes += obytes / (16 / BYTES_PER_FRAME);
count += chunk;
@@ -632,7 +646,7 @@ static void output_thread_i2s(void *arg) {
}
/****************************************************************************************
* Stats output thread
* stats output callback
*/
static void i2s_stats(uint32_t now) {
static uint32_t last;
@@ -643,7 +657,7 @@ static void i2s_stats(uint32_t 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);
@@ -667,47 +681,45 @@ static void i2s_stats(uint32_t now) {
/****************************************************************************************
* SPDIF support
*/
#define PREAMBLE_B (0xE8) //11101000
#define PREAMBLE_M (0xE2) //11100010
#define PREAMBLE_W (0xE4) //11100100
#define VUCP ((0xCC) << 24)
#define VUCP_MUTE ((0xD4) << 24) // To mute PCM, set VUCP = invalid.
static const u8_t VUCP24[2] = { 0xCC, 0x32 };
static const u16_t spdif_bmclookup[256] = { //biphase mark encoded values (least significant bit first)
0xcccc, 0x4ccc, 0x2ccc, 0xaccc, 0x34cc, 0xb4cc, 0xd4cc, 0x54cc,
0x32cc, 0xb2cc, 0xd2cc, 0x52cc, 0xcacc, 0x4acc, 0x2acc, 0xaacc,
0x334c, 0xb34c, 0xd34c, 0x534c, 0xcb4c, 0x4b4c, 0x2b4c, 0xab4c,
0xcd4c, 0x4d4c, 0x2d4c, 0xad4c, 0x354c, 0xb54c, 0xd54c, 0x554c,
0x332c, 0xb32c, 0xd32c, 0x532c, 0xcb2c, 0x4b2c, 0x2b2c, 0xab2c,
0xcd2c, 0x4d2c, 0x2d2c, 0xad2c, 0x352c, 0xb52c, 0xd52c, 0x552c,
0xccac, 0x4cac, 0x2cac, 0xacac, 0x34ac, 0xb4ac, 0xd4ac, 0x54ac,
0x32ac, 0xb2ac, 0xd2ac, 0x52ac, 0xcaac, 0x4aac, 0x2aac, 0xaaac,
0x3334, 0xb334, 0xd334, 0x5334, 0xcb34, 0x4b34, 0x2b34, 0xab34,
0xcd34, 0x4d34, 0x2d34, 0xad34, 0x3534, 0xb534, 0xd534, 0x5534,
0xccb4, 0x4cb4, 0x2cb4, 0xacb4, 0x34b4, 0xb4b4, 0xd4b4, 0x54b4,
0x32b4, 0xb2b4, 0xd2b4, 0x52b4, 0xcab4, 0x4ab4, 0x2ab4, 0xaab4,
0xccd4, 0x4cd4, 0x2cd4, 0xacd4, 0x34d4, 0xb4d4, 0xd4d4, 0x54d4,
0x32d4, 0xb2d4, 0xd2d4, 0x52d4, 0xcad4, 0x4ad4, 0x2ad4, 0xaad4,
0x3354, 0xb354, 0xd354, 0x5354, 0xcb54, 0x4b54, 0x2b54, 0xab54,
0xcd54, 0x4d54, 0x2d54, 0xad54, 0x3554, 0xb554, 0xd554, 0x5554,
0x3332, 0xb332, 0xd332, 0x5332, 0xcb32, 0x4b32, 0x2b32, 0xab32,
0xcd32, 0x4d32, 0x2d32, 0xad32, 0x3532, 0xb532, 0xd532, 0x5532,
0xccb2, 0x4cb2, 0x2cb2, 0xacb2, 0x34b2, 0xb4b2, 0xd4b2, 0x54b2,
0x32b2, 0xb2b2, 0xd2b2, 0x52b2, 0xcab2, 0x4ab2, 0x2ab2, 0xaab2,
0xccd2, 0x4cd2, 0x2cd2, 0xacd2, 0x34d2, 0xb4d2, 0xd4d2, 0x54d2,
0x32d2, 0xb2d2, 0xd2d2, 0x52d2, 0xcad2, 0x4ad2, 0x2ad2, 0xaad2,
0x3352, 0xb352, 0xd352, 0x5352, 0xcb52, 0x4b52, 0x2b52, 0xab52,
0xcd52, 0x4d52, 0x2d52, 0xad52, 0x3552, 0xb552, 0xd552, 0x5552,
0xccca, 0x4cca, 0x2cca, 0xacca, 0x34ca, 0xb4ca, 0xd4ca, 0x54ca,
0x32ca, 0xb2ca, 0xd2ca, 0x52ca, 0xcaca, 0x4aca, 0x2aca, 0xaaca,
0x334a, 0xb34a, 0xd34a, 0x534a, 0xcb4a, 0x4b4a, 0x2b4a, 0xab4a,
0xcd4a, 0x4d4a, 0x2d4a, 0xad4a, 0x354a, 0xb54a, 0xd54a, 0x554a,
0x332a, 0xb32a, 0xd32a, 0x532a, 0xcb2a, 0x4b2a, 0x2b2a, 0xab2a,
0xcd2a, 0x4d2a, 0x2d2a, 0xad2a, 0x352a, 0xb52a, 0xd52a, 0x552a,
0xccaa, 0x4caa, 0x2caa, 0xacaa, 0x34aa, 0xb4aa, 0xd4aa, 0x54aa,
0x32aa, 0xb2aa, 0xd2aa, 0x52aa, 0xcaaa, 0x4aaa, 0x2aaa, 0xaaaa
static const u16_t spdif_bmclookup[256] = {
0xcccc, 0xb333, 0xd333, 0xaccc, 0xcb33, 0xb4cc, 0xd4cc, 0xab33,
0xcd33, 0xb2cc, 0xd2cc, 0xad33, 0xcacc, 0xb533, 0xd533, 0xaacc,
0xccb3, 0xb34c, 0xd34c, 0xacb3, 0xcb4c, 0xb4b3, 0xd4b3, 0xab4c,
0xcd4c, 0xb2b3, 0xd2b3, 0xad4c, 0xcab3, 0xb54c, 0xd54c, 0xaab3,
0xccd3, 0xb32c, 0xd32c, 0xacd3, 0xcb2c, 0xb4d3, 0xd4d3, 0xab2c,
0xcd2c, 0xb2d3, 0xd2d3, 0xad2c, 0xcad3, 0xb52c, 0xd52c, 0xaad3,
0xccac, 0xb353, 0xd353, 0xacac, 0xcb53, 0xb4ac, 0xd4ac, 0xab53,
0xcd53, 0xb2ac, 0xd2ac, 0xad53, 0xcaac, 0xb553, 0xd553, 0xaaac,
0xcccb, 0xb334, 0xd334, 0xaccb, 0xcb34, 0xb4cb, 0xd4cb, 0xab34,
0xcd34, 0xb2cb, 0xd2cb, 0xad34, 0xcacb, 0xb534, 0xd534, 0xaacb,
0xccb4, 0xb34b, 0xd34b, 0xacb4, 0xcb4b, 0xb4b4, 0xd4b4, 0xab4b,
0xcd4b, 0xb2b4, 0xd2b4, 0xad4b, 0xcab4, 0xb54b, 0xd54b, 0xaab4,
0xccd4, 0xb32b, 0xd32b, 0xacd4, 0xcb2b, 0xb4d4, 0xd4d4, 0xab2b,
0xcd2b, 0xb2d4, 0xd2d4, 0xad2b, 0xcad4, 0xb52b, 0xd52b, 0xaad4,
0xccab, 0xb354, 0xd354, 0xacab, 0xcb54, 0xb4ab, 0xd4ab, 0xab54,
0xcd54, 0xb2ab, 0xd2ab, 0xad54, 0xcaab, 0xb554, 0xd554, 0xaaab,
0xcccd, 0xb332, 0xd332, 0xaccd, 0xcb32, 0xb4cd, 0xd4cd, 0xab32,
0xcd32, 0xb2cd, 0xd2cd, 0xad32, 0xcacd, 0xb532, 0xd532, 0xaacd,
0xccb2, 0xb34d, 0xd34d, 0xacb2, 0xcb4d, 0xb4b2, 0xd4b2, 0xab4d,
0xcd4d, 0xb2b2, 0xd2b2, 0xad4d, 0xcab2, 0xb54d, 0xd54d, 0xaab2,
0xccd2, 0xb32d, 0xd32d, 0xacd2, 0xcb2d, 0xb4d2, 0xd4d2, 0xab2d,
0xcd2d, 0xb2d2, 0xd2d2, 0xad2d, 0xcad2, 0xb52d, 0xd52d, 0xaad2,
0xccad, 0xb352, 0xd352, 0xacad, 0xcb52, 0xb4ad, 0xd4ad, 0xab52,
0xcd52, 0xb2ad, 0xd2ad, 0xad52, 0xcaad, 0xb552, 0xd552, 0xaaad,
0xccca, 0xb335, 0xd335, 0xacca, 0xcb35, 0xb4ca, 0xd4ca, 0xab35,
0xcd35, 0xb2ca, 0xd2ca, 0xad35, 0xcaca, 0xb535, 0xd535, 0xaaca,
0xccb5, 0xb34a, 0xd34a, 0xacb5, 0xcb4a, 0xb4b5, 0xd4b5, 0xab4a,
0xcd4a, 0xb2b5, 0xd2b5, 0xad4a, 0xcab5, 0xb54a, 0xd54a, 0xaab5,
0xccd5, 0xb32a, 0xd32a, 0xacd5, 0xcb2a, 0xb4d5, 0xd4d5, 0xab2a,
0xcd2a, 0xb2d5, 0xd2d5, 0xad2a, 0xcad5, 0xb52a, 0xd52a, 0xaad5,
0xccaa, 0xb355, 0xd355, 0xacaa, 0xcb55, 0xb4aa, 0xd4aa, 0xab55,
0xcd55, 0xb2aa, 0xd2aa, 0xad55, 0xcaaa, 0xb555, 0xd555, 0xaaaa
};
/*
@@ -723,65 +735,69 @@ static const u16_t spdif_bmclookup[256] = { //biphase mark encoded values (least
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;
size_t cnt = *count;
static void IRAM_ATTR spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst) {
static u8_t vu, count;
register u16_t hi, lo;
#if BYTES_PER_FRAME == 8
register u16_t aux;
#endif
// we assume frame == 0 as well...
if (!src) {
count = 192;
vu = VUCP24[0];
}
while (frames--) {
// start with left channel
#if BYTES_PER_FRAME == 4
hi = spdif_bmclookup[(u8_t)(*src >> 8)];
lo = spdif_bmclookup[(u8_t) *src++];
// invert if last preceeding bit is 1
lo ^= ~((s16_t)hi) >> 16;
// first 16 bits
aux = 0xb333 ^ (((u32_t)((s16_t)lo)) >> 17);
#else
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
// invert if last preceeding bit is 1
lo ^= ~((s16_t)hi) >> 16;
// first 16 bits
// 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
// set special preamble every 192 iteration
if (++cnt > 191) {
*dst++ = VUCP | (PREAMBLE_B << 16 ) | aux; //special preamble for one of 192 frames
cnt = 0;
hi = spdif_bmclookup[(u8_t)(*src >> 8)];
lo = spdif_bmclookup[(u8_t)*src++];
if (lo & 1) hi = ~hi;
if (!count--) {
*dst++ = (vu << 24) | (PREAMBLE_B << 16) | 0xCCCC;
count = 192;
} else {
*dst++ = VUCP | (PREAMBLE_M << 16) | aux;
}
// now write sample's 16 low bits
*dst++ = ((u32_t)lo << 16) | hi;
*dst++ = (vu << 24) | (PREAMBLE_M << 16) | 0xCCCC;
}
#else
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
aux = spdif_bmclookup[(u8_t)(*src++ >> 8)];
if (aux & 1) lo = ~lo;
if (lo & 1) hi = ~hi;
if (!count--) {
*dst++ = (vu << 24) | (PREAMBLE_B << 16) | aux;
count = 192;
} else {
*dst++ = (vu << 24) | (PREAMBLE_M << 16) | aux;
}
#endif
vu = VUCP24[hi & 1];
*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;
aux = 0xb333 ^ (((u32_t)((s16_t)lo)) >> 17);
hi = spdif_bmclookup[(u8_t)(*src >> 8)];
lo = spdif_bmclookup[(u8_t)*src++];
if (lo & 1) hi = ~hi;
*dst++ = (vu << 24) | (PREAMBLE_W << 16) | 0xCCCC;
#else
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
lo ^= ~((s16_t)hi) >> 16;
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;
hi = spdif_bmclookup[(u8_t)(*src >> 24)];
lo = spdif_bmclookup[(u8_t)(*src >> 16)];
aux = spdif_bmclookup[(u8_t)(*src++ >> 8)];
if (aux & 1) lo = ~lo;
if (lo & 1) hi = ~hi;
*dst++ = (vu << 24) | (PREAMBLE_W << 16) | aux;
#endif
vu = VUCP24[hi & 1];
*dst++ = ((u32_t)lo << 16) | hi;
}
*count = cnt;
}

View File

@@ -48,8 +48,8 @@ void ws2812_write_leds(struct led_state new_state);
static const char TAG[] = "muse";
static void (*battery_handler_chain)(float value);
static void battery_svc(float value);
static void (*battery_handler_chain)(float value, int cells);
static void battery_svc(float value, int cells);
static bool init(void);
static void set_battery_led(float value);
@@ -81,11 +81,11 @@ static void set_battery_led(float value) {
ws2812_write_leds(new_state);
}
static void battery_svc(float value) {
static void battery_svc(float value, int cells) {
set_battery_led(value);
ESP_LOGI(TAG, "Called for battery service with %f", value);
if (battery_handler_chain) battery_handler_chain(value);
if (battery_handler_chain) battery_handler_chain(value, cells);
}
// This is the buffer which the hw peripheral will access while pulsing the output pin
@@ -95,7 +95,7 @@ void setup_rmt_data_buffer(struct led_state new_state);
void ws2812_control_init(void)
{
rmt_channel = rmt_system_base_channel++;
rmt_channel = RMT_NEXT_TX_CHANNEL();
rmt_config_t config;
config.rmt_mode = RMT_MODE_TX;
config.channel = rmt_channel;

View File

@@ -1,4 +1,4 @@
/*
/*
* (c) Philippe G. 20201, philippe_44@outlook.com
* see other copyrights below
*
@@ -20,6 +20,10 @@
#include "esp_log.h"
#include "tools.h"
#if CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS < 2
#error CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS must be at least 2
#endif
const static char TAG[] = "tools";
/****************************************************************************************
@@ -104,11 +108,11 @@ static uint8_t UNICODEtoCP1252(uint16_t chr) {
void utf8_decode(char *src) {
uint32_t codep = 0, state = UTF8_ACCEPT;
char *dst = src;
while (src && *src) {
if (!decode(&state, &codep, *src++)) *dst++ = UNICODEtoCP1252(codep);
}
*dst = '\0';
}
@@ -178,12 +182,61 @@ char * strdup_psram(const char * source){
}
/****************************************************************************************
* URL download
* Task manager
*/
#define TASK_TLS_INDEX 1
typedef struct {
StaticTask_t *xTaskBuffer;
StackType_t *xStack;
} task_context_t;
static void task_cleanup(int index, task_context_t *context) {
free(context->xTaskBuffer);
free(context->xStack);
free(context);
}
BaseType_t xTaskCreateEXTRAM( TaskFunction_t pvTaskCode,
const char * const pcName,
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask) {
// create the worker task as a static
task_context_t *context = calloc(1, sizeof(task_context_t));
context->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
context->xStack = heap_caps_malloc(usStackDepth,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
TaskHandle_t handle = xTaskCreateStatic(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, context->xStack, context->xTaskBuffer);
// store context in TCB or free everything in case of failure
if (!handle) {
free(context->xTaskBuffer);
free(context->xStack);
free(context);
} else {
vTaskSetThreadLocalStoragePointerAndDelCallback( handle, TASK_TLS_INDEX, context, (TlsDeleteCallbackFunction_t) task_cleanup);
}
if (pxCreatedTask) *pxCreatedTask = handle;
return handle != NULL ? pdPASS : pdFAIL;
}
void vTaskDeleteEXTRAM(TaskHandle_t xTask) {
/* At this point we leverage FreeRTOS extension to have callbacks on task deletion.
* If not, we need to have here our own deletion implementation that include delayed
* free for when this is called with NULL (self-deletion)
*/
vTaskDelete(xTask);
}
/****************************************************************************************
* URL download
*/
typedef struct {
void *user_context;
http_download_cb_t callback;
http_download_cb_t callback;
size_t max, bytes;
bool abort;
uint8_t *data;
@@ -192,22 +245,22 @@ typedef struct {
static void http_downloader(void *arg);
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
void http_download(char *url, size_t max, http_download_cb_t callback, void *context) {
http_context_t *http_context = (http_context_t*) heap_caps_calloc(sizeof(http_context_t), 1, MALLOC_CAP_SPIRAM);
esp_http_client_config_t config = {
.url = url,
.event_handler = http_event_handler,
.user_data = http_context,
};
};
http_context->callback = callback;
http_context->user_context = context;
http_context->max = max;
http_context->client = esp_http_client_init(&config);
xTaskCreate(http_downloader, "downloader", 4*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL);
xTaskCreateEXTRAM(http_downloader, "downloader", 8*1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL);
}
static void http_downloader(void *arg) {
@@ -215,14 +268,14 @@ static void http_downloader(void *arg) {
esp_http_client_perform(http_context->client);
esp_http_client_cleanup(http_context->client);
free(http_context);
vTaskDelete(NULL);
vTaskDeleteEXTRAM(NULL);
}
static esp_err_t http_event_handler(esp_http_client_event_t *evt) {
http_context_t *http_context = (http_context_t*) evt->user_data;
if (http_context->abort) return ESP_FAIL;
switch(evt->event_id) {
@@ -234,42 +287,42 @@ static esp_err_t http_event_handler(esp_http_client_event_t *evt) {
if (!strcasecmp(evt->header_key, "Content-Length")) {
size_t len = atoi(evt->header_value);
if (!len || len > http_context->max) {
ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max);
ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max);
http_context->abort = true;
}
}
}
}
break;
case HTTP_EVENT_ON_DATA: {
size_t len = esp_http_client_get_content_length(evt->client);
if (!http_context->data) {
if ((http_context->data = (uint8_t*) malloc(len)) == NULL) {
http_context->abort = true;
ESP_LOGE(TAG, "gailed to allocate memory for output buffer %zu", len);
ESP_LOGE(TAG, "failed to allocate memory for output buffer %zu", len);
return ESP_FAIL;
}
}
}
}
memcpy(http_context->data + http_context->bytes, evt->data, evt->data_len);
http_context->bytes += evt->data_len;
break;
}
}
case HTTP_EVENT_ON_FINISH:
http_context->callback(http_context->data, http_context->bytes, http_context->user_context);
http_context->callback(http_context->data, http_context->bytes, http_context->user_context);
break;
case HTTP_EVENT_DISCONNECTED: {
int mbedtls_err = 0;
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP download disconnect %d", err);
ESP_LOGE(TAG, "HTTP download disconnect %d", err);
if (http_context->data) free(http_context->data);
http_context->callback(NULL, 0, http_context->user_context);
http_context->callback(NULL, 0, http_context->user_context);
return ESP_FAIL;
}
break;
default:
break;
}
}
}
return ESP_OK;
}

View File

@@ -56,6 +56,18 @@ const char* str_or_null(const char * str);
typedef void (*http_download_cb_t)(uint8_t* data, size_t len, void *context);
void http_download(char *url, size_t max, http_download_cb_t callback, void *context);
/* Use these to dynamically create tasks whose stack is on EXTRAM. Be aware that it
* requires configNUM_THREAD_LOCAL_STORAGE_POINTERS to bet set to 2 at least (index 0
* is used by pthread and this uses index 1, obviously
*/
BaseType_t xTaskCreateEXTRAM( TaskFunction_t pvTaskCode,
const char * const pcName,
configSTACK_DEPTH_TYPE usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask);
void vTaskDeleteEXTRAM(TaskHandle_t xTask);
extern const char unknown_string_placeholder[];
#ifdef __cplusplus

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

File diff suppressed because one or more lines are too long

View File

@@ -681,7 +681,7 @@ def handle_build_flags(args):
github_env.mock = 1 if args.mock else 0
github_env.release_flag = 1 if args.mock or args.force or 'release' in commit_message.lower() else 0
github_env.ui_build = 1 if args.mock or args.ui_build or '[ui-build]' in commit_message.lower(
) or github_env.release_flag == 1 else 0
) else 0
write_github_env_file(github_env,os.environ.get('GITHUB_OUTPUT'))
def write_version_number(file_path:str,env_details):

View File

@@ -73,6 +73,7 @@ static bool bNetworkConnected=false;
// as an exception _init function don't need include
extern void services_init(void);
extern void services_sleep_init(void);
extern void display_init(char *welcome);
extern void led_vu_init(void);
extern void target_init(char *target);
@@ -270,6 +271,7 @@ void register_default_nvs(){
register_default_string_val( "i2c_config", CONFIG_I2C_CONFIG);
register_default_string_val( "spi_config", CONFIG_SPI_CONFIG);
register_default_string_val( "set_GPIO", CONFIG_SET_GPIO);
register_default_string_val( "sleep_config", "");
register_default_string_val( "led_brightness", "");
register_default_string_val( "spdif_config", "");
register_default_string_val( "dac_config", "");
@@ -462,5 +464,6 @@ void app_main()
}
free(fwurl);
}
services_sleep_init();
messaging_post_message(MESSAGING_INFO,MESSAGING_CLASS_SYSTEM,"System started");
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
server_certs/r2m01.cer.25 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.26 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.27 Normal file

Binary file not shown.