mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-16 00:17:03 +03:00
Compare commits
203 Commits
I2S-4MFlas
...
I2S-4MFlas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6723dfa2f | ||
|
|
ffaff5ac27 | ||
|
|
33ef4b01e7 | ||
|
|
302865b167 | ||
|
|
7f0ae69e81 | ||
|
|
e21e2cf7f9 | ||
|
|
57cd009e4c | ||
|
|
78e8d60021 | ||
|
|
4068e07a45 | ||
|
|
a633524936 | ||
|
|
9d71b8ee26 | ||
|
|
672aca8258 | ||
|
|
a2351ba0d5 | ||
|
|
40a698e2f1 | ||
|
|
38d28ae8c4 | ||
|
|
21407e8c1c | ||
|
|
12aa555ff3 | ||
|
|
cb47ec855b | ||
|
|
787a5d9a6e | ||
|
|
7b9deb795c | ||
|
|
4b1f8a8d4b | ||
|
|
4f8661100b | ||
|
|
6b2eb1b3c0 | ||
|
|
f3593fa2f4 | ||
|
|
ccc7b86369 | ||
|
|
9e3c6dcf30 | ||
|
|
7be81887a6 | ||
|
|
29f71fc677 | ||
|
|
a14a6edc1b | ||
|
|
35bf0a3c10 | ||
|
|
55a8658f3a | ||
|
|
b0d9e1668c | ||
|
|
922367baf5 | ||
|
|
01320db007 | ||
|
|
f677695fc7 | ||
|
|
7902af2bf0 | ||
|
|
5faecb08f6 | ||
|
|
8e3d91020f | ||
|
|
4f54a47c83 | ||
|
|
58840f894f | ||
|
|
94109ebf38 | ||
|
|
fc20618fa2 | ||
|
|
70720d3445 | ||
|
|
4abe1304e8 | ||
|
|
be7fb4b669 | ||
|
|
3554af1460 | ||
|
|
02d422c1f4 | ||
|
|
343b728323 | ||
|
|
9679c5c104 | ||
|
|
5c90086bbd | ||
|
|
807a0e547a | ||
|
|
1d54331224 | ||
|
|
e85a3cddf1 | ||
|
|
d4d97d5a60 | ||
|
|
2afbee7cb1 | ||
|
|
bbf9a3af70 | ||
|
|
a9ca4c4450 | ||
|
|
55bce084eb | ||
|
|
d31697e7ef | ||
|
|
e830b4db73 | ||
|
|
8fe21327b8 | ||
|
|
3f487366ee | ||
|
|
b875585aec | ||
|
|
b46fccfc83 | ||
|
|
655c17fb29 | ||
|
|
b8bda0435a | ||
|
|
dc5cb31efb | ||
|
|
44ccbb49a6 | ||
|
|
6589387cd3 | ||
|
|
0d3f6a8870 | ||
|
|
bb6ec4a629 | ||
|
|
a029304776 | ||
|
|
dad8efff8b | ||
|
|
e8c6169e09 | ||
|
|
fc8d15f58d | ||
|
|
f9d7e15d4b | ||
|
|
67722bef94 | ||
|
|
8ea777ddff | ||
|
|
ad8d587e94 | ||
|
|
667b90fafc | ||
|
|
7be653f722 | ||
|
|
f6269a2f7a | ||
|
|
4b70514fa3 | ||
|
|
00a0b8c36e | ||
|
|
9f4474a19c | ||
|
|
58fac99e95 | ||
|
|
cb7c8fd6be | ||
|
|
b9a1bf0432 | ||
|
|
63658efefe | ||
|
|
c76bbc3524 | ||
|
|
9619b1d792 | ||
|
|
0bbd5a064f | ||
|
|
986521fd4a | ||
|
|
406a56a3a3 | ||
|
|
c9455f70ff | ||
|
|
b5b76480e4 | ||
|
|
e6744deab8 | ||
|
|
94baf86989 | ||
|
|
0c856a37c1 | ||
|
|
d5f28375ce | ||
|
|
7ea5a93647 | ||
|
|
ca38a14420 | ||
|
|
9ebe717e74 | ||
|
|
12347bdb29 | ||
|
|
a42c9ff860 | ||
|
|
e67413697c | ||
|
|
c4b797e54f | ||
|
|
9971fb0ff3 | ||
|
|
8280bc4903 | ||
|
|
adc6a86725 | ||
|
|
61f58f9a52 | ||
|
|
e3650413f5 | ||
|
|
750ffbf464 | ||
|
|
65f52a23bc | ||
|
|
da411bf1c8 | ||
|
|
0a319269c2 | ||
|
|
b0ce38bf14 | ||
|
|
767b677947 | ||
|
|
724818390b | ||
|
|
105e800cc1 | ||
|
|
ab09f009f7 | ||
|
|
9c179adf85 | ||
|
|
719b289659 | ||
|
|
eb7df4a5e9 | ||
|
|
9a54239323 | ||
|
|
b790156be0 | ||
|
|
f87b3adec2 | ||
|
|
35d42f2096 | ||
|
|
d2d0cadeed | ||
|
|
cd76619e96 | ||
|
|
781699362f | ||
|
|
c4dbf60cb6 | ||
|
|
3dd7b4b02c | ||
|
|
83c1a2b8e0 | ||
|
|
e8021c38f0 | ||
|
|
71bb57f1eb | ||
|
|
7eb4b218e3 | ||
|
|
953d1657f9 | ||
|
|
b21a143196 | ||
|
|
898f1d92ed | ||
|
|
f6fd11783c | ||
|
|
f33cb569ce | ||
|
|
f5d6f26c01 | ||
|
|
8df6b853e6 | ||
|
|
f7c0107d40 | ||
|
|
5068309d25 | ||
|
|
506a5aaf7a | ||
|
|
fe409730e0 | ||
|
|
4d6cfaca1c | ||
|
|
69e61ce451 | ||
|
|
60bd591bf8 | ||
|
|
3ea26a0c6f | ||
|
|
53fa83b2dd | ||
|
|
90f53db953 | ||
|
|
b413780048 | ||
|
|
456f16fc79 | ||
|
|
04e2917351 | ||
|
|
dc9e1191a2 | ||
|
|
72a8fb2249 | ||
|
|
f409a9ee28 | ||
|
|
b85cf98cdf | ||
|
|
e85c967220 | ||
|
|
ce21ff1b76 | ||
|
|
7c71fb6a65 | ||
|
|
bb22d4ea02 | ||
|
|
e0e749fb5b | ||
|
|
c650bc7658 | ||
|
|
a76ce3f344 | ||
|
|
1d059be001 | ||
|
|
32195b50ba | ||
|
|
a7539a5332 | ||
|
|
450ebae399 | ||
|
|
d238063c49 | ||
|
|
58f2e4488b | ||
|
|
b83e2722c0 | ||
|
|
96a3f8aab0 | ||
|
|
66b88d186a | ||
|
|
bb185d76dc | ||
|
|
f4c0a91e84 | ||
|
|
2ecf2e6098 | ||
|
|
f32e4de84b | ||
|
|
55303ec1b3 | ||
|
|
effc574e50 | ||
|
|
3941a26b67 | ||
|
|
b47074e668 | ||
|
|
822de92df1 | ||
|
|
6bd778c7c6 | ||
|
|
28ecad4652 | ||
|
|
ddd6bddde7 | ||
|
|
f4e899fc60 | ||
|
|
c61ff05081 | ||
|
|
c4df0c93f9 | ||
|
|
fae09ba29e | ||
|
|
fd0c38c49f | ||
|
|
a83f14113e | ||
|
|
7f0b411dac | ||
|
|
3350a8dbc7 | ||
|
|
a2eddb5411 | ||
|
|
65ff5f7c2a | ||
|
|
5ed2f6d03e | ||
|
|
d8bd588982 | ||
|
|
e4481a95f9 | ||
|
|
992f5c361c |
15
.github/workflows/Platform_build.yml
vendored
15
.github/workflows/Platform_build.yml
vendored
@@ -34,14 +34,15 @@ 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=""
|
||||
echo "ui_build_option=$ui_build_option" >> $GITHUB_ENV
|
||||
echo "release_build_option=$release_build_option" >> $GITHUB_ENV
|
||||
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
|
||||
echo "ui_build_option=$ui_build_option" >> "$GITHUB_OUTPUT"
|
||||
echo "release_build_option=$release_build_option" >> "$GITHUB_OUTPUT"
|
||||
echo "Dumping environment"
|
||||
env
|
||||
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
|
||||
@@ -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"
|
||||
@@ -87,7 +88,7 @@ jobs:
|
||||
git commit -m "Update prebuilt objects [skip actions]"
|
||||
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
|
||||
- name: Locally store commonly built objects
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prebuilt_objects
|
||||
path: |
|
||||
@@ -129,7 +130,7 @@ jobs:
|
||||
git status
|
||||
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435
|
||||
|
||||
- uses: actions/download-artifact@master
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Restore common objects
|
||||
with:
|
||||
name: prebuilt_objects
|
||||
@@ -196,7 +197,7 @@ jobs:
|
||||
release_name: ${{ env.name }}
|
||||
body: ${{ env.description }}
|
||||
draft: false
|
||||
prerelease: true
|
||||
prerelease: false
|
||||
- name: Upload Release Asset - Squeezelite binary file
|
||||
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
|
||||
id: upload-release-asset
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -8,3 +8,6 @@
|
||||
[submodule "components/wifi-manager/UML-State-Machine-in-C"]
|
||||
path = components/wifi-manager/UML-State-Machine-in-C
|
||||
url = https://github.com/kiishor/UML-State-Machine-in-C
|
||||
[submodule "components/wifi-manager/webapp/src/bootswatch"]
|
||||
path = components/wifi-manager/webapp/src/bootswatch
|
||||
url = https://github.com/thomaspark/bootswatch.git
|
||||
|
||||
92
CHANGELOG
Normal file
92
CHANGELOG
Normal file
@@ -0,0 +1,92 @@
|
||||
2024-09-12
|
||||
- add AW9523 GPIO expander credits @Stefan Krupop (https://github.com/sle118/squeezelite-esp32/pull/430
|
||||
|
||||
2024-09-10
|
||||
- Merge pull request # 439 from digidocs/eq_update_fix2 (# 309)
|
||||
- Fix for I2S noise burst when ESP32 panic occurs (# 437)
|
||||
|
||||
2024-05-05
|
||||
- Fix crash when led_vu is configured without display
|
||||
2024-01-27
|
||||
- complete libflac fix and add chaining enablement
|
||||
- fixed stream Ogg demux issue with unknown granule
|
||||
|
||||
2024-01-19
|
||||
- fixed libflac with OggFlac
|
||||
- AirPlay missed frame logging
|
||||
|
||||
2024-01-16
|
||||
- catch-up with cspot latest
|
||||
- refactor airplay flush/first packet
|
||||
- new libFLAC that supports multi-stream OggFlac
|
||||
- fix output threshold
|
||||
- log missed frames
|
||||
|
||||
2024-01-10
|
||||
- add OggFlac to stream metadata
|
||||
- fix OggFlac deadlock in flac callback when not enough data in streambuf
|
||||
- fix no displayer due to threadshold too high (use 500ms instead)
|
||||
- reset outputbuf when cspot starts
|
||||
|
||||
2024-01-01
|
||||
- ogg stream are parsed to foward metadata to LMS
|
||||
- fix some ogg parsing on multi-stream containers
|
||||
|
||||
2023-11-19
|
||||
- more robust (?) airplay RTP frame recovery
|
||||
- initialize of scratch string in monitor (trying to figure out random reboot)
|
||||
|
||||
2023-11-16
|
||||
- add SH1122 support
|
||||
- optimize GDS DrawPixel function
|
||||
|
||||
2023-11-09
|
||||
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
|
||||
|
||||
2023-11-08
|
||||
- execute dac_controlset even when there is no i2s (for gpio)
|
||||
|
||||
2023-11-07
|
||||
- led-vu gain + misc fixes
|
||||
- bump plugin version to 0.600
|
||||
|
||||
2023-11-03
|
||||
- don't reboot when external decoder is connected even with a LMS server
|
||||
|
||||
2023-10-28
|
||||
- fix recovery size (remove bootstrap)
|
||||
- improve NVS initialization structure
|
||||
|
||||
2023-10-27
|
||||
- fix vorbis (and opus) memory leak
|
||||
|
||||
2023-10-25
|
||||
- fix vorbis codec close
|
||||
|
||||
2023-10-23
|
||||
- fix Spotify track insertion
|
||||
- [WEB] Allow running without LMS with option "Audio/Disable Squeezelite"
|
||||
|
||||
2023-10.07
|
||||
- catchup with official cspot
|
||||
|
||||
2023-10-06
|
||||
- fix cspot PREV on first track, NEXT on last track and normal ending
|
||||
- use DMA_AUTO for SPI
|
||||
- cspot share same time log
|
||||
|
||||
2023-10-06
|
||||
- Fix bootswatch bug that caused difficult to read UI ( issue #319)
|
||||
|
||||
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
|
||||
@@ -73,7 +73,7 @@ set_target_properties(recovery.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_re
|
||||
# build squeezelite, add app_squeezelite to the link
|
||||
add_executable(squeezelite.elf "CMakeLists.txt")
|
||||
add_dependencies(squeezelite.elf recovery.elf)
|
||||
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map")
|
||||
set_target_properties(squeezelite.elf PROPERTIES LINK_LIBRARIES "${BCA};idf::app_squeezelite;-Wl,--Map=${BUILD_DIR}/squeezelite.map,--wrap=esp_panic_handler")
|
||||
add_custom_command(
|
||||
TARGET squeezelite.elf
|
||||
POST_BUILD
|
||||
@@ -228,4 +228,4 @@ endif()
|
||||
# target_compile_definitions(__idf_wear_levelling PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_wifi_provisioning PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_wpa_supplicant PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
# target_compile_definitions(__idf_xtensa PRIVATE -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG)
|
||||
|
||||
@@ -25,7 +25,11 @@ ENV GCC_TOOLS_BASE=/opt/esp/tools/xtensa-esp32-elf/esp-2021r2-patch3-8.4.0/xtens
|
||||
# pushd components/wifi-manager/webapp/ && npm install && npm run-script build && popd
|
||||
#
|
||||
# to run the docker with netwotrk port published on the host:
|
||||
# (windows)
|
||||
# docker run --rm -p 5000:5000/tcp -v %cd%:/project -w /project -it sle118/squeezelite-esp32-idfv435
|
||||
# (linux)
|
||||
# docker run --rm -p 5000:5000/tcp -v `pwd`:/project -w /project -it sle118/squeezelite-esp32-idfv435
|
||||
|
||||
|
||||
ARG IDF_CLONE_URL=https://github.com/espressif/esp-idf.git
|
||||
ARG IDF_CLONE_BRANCH_OR_TAG=master
|
||||
|
||||
115
README.md
115
README.md
@@ -1,13 +1,14 @@
|
||||
[](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml)
|
||||
# Squeezelite-esp32
|
||||
|
||||
## Forewords
|
||||
**More and more people seems to use this without a LMS server, just for BT, AirPlay or Spotify. It's fine but understand that squeezeliteESP32 is primarily a Logitech Media Server player and has been designed around that concept. All the others are add-ons stitched to it, so other modes have their shortcomings. So please make sure you read [this](#Additional-configuration-notes-from-the-Web-UI) before opening an issue**
|
||||
## What is this?
|
||||
Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. It offers the following capabilities
|
||||
|
||||
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
|
||||
- Stream from a **Bluetooth** device (iPhone, Android)
|
||||
- Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
|
||||
- Stream 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 +70,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 +83,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.
|
||||
|
||||
@@ -184,15 +187,23 @@ bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS
|
||||
```
|
||||
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
|
||||
|
||||
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speakder and headset on and off using a JSON syntax:
|
||||
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
|
||||
```json
|
||||
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
|
||||
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
|
||||
{ <command>: [ <item1>, <item2>, ... <item3> ],
|
||||
<command>: [ <item1>, <item2>, ... <item3> ],
|
||||
... }
|
||||
```
|
||||
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff
|
||||
Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff (it **must** be an array even for a single item). Item is any of the following elements
|
||||
```
|
||||
{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}
|
||||
{"gpio":<gpio>,"level":0|1}
|
||||
{"delay":<ms>}
|
||||
```
|
||||
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here.
|
||||
|
||||
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
|
||||
The `reg` key allow to write registers on i2c bus. The parameter `mode` allows to *or* the register with the value or to *and* it. Don't set `mode` if you simply want to write. The `val` parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax. The `gpio` key is simply to set a gpio as part of DAC action and `delay` allows a pause between elements.
|
||||
|
||||
The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
|
||||
|
||||
NB: For named configurations ((SqueezeAMP, Muse ... all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
|
||||
|
||||
@@ -211,7 +222,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 +238,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.
|
||||
@@ -272,7 +283,7 @@ GPIO can be set to GND provide or Vcc at boot. This is convenient to power devic
|
||||
|
||||
The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
|
||||
|
||||
The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off
|
||||
The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off (in LMS, does not apply to AirPlay, Spotify or BT).
|
||||
|
||||
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
|
||||
|
||||
@@ -297,7 +308,7 @@ The parameter "gpio_exp_config" is a semicolon (;) separated list with following
|
||||
```
|
||||
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
|
||||
```
|
||||
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
|
||||
- model: pca9535, pca85xx, mcp23017, aw9523 and mcp23s17 (SPI version)
|
||||
- addr: chip i2c/spi address (decimal)
|
||||
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
|
||||
- cs (SPI): gpio used for Chip Select
|
||||
@@ -316,18 +327,18 @@ See [set_GPIO](#set-gpio) for how to set the green and red LEDs (including addre
|
||||
NB: For named configuration, GPIO affected to green and red LED cannot be changed but brightness option applies
|
||||
|
||||
### LED Strip
|
||||
One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The LED strip can also be controlled remotely though the LMS server (using the CLI interface). Currently only WS2812B LEDs are supported. Set the LED Strip configuration (or NVS led_vu_config) to `WS2812,length=<n>,gpio=<gpio>, where <n> is the number of leds in the strip (1..255), and <gpio> is the data pin.`
|
||||
|
||||
The latest LMS plugin update is required to set the visualizer mode and brightness, in the ESP32 settings page for the player. The plugin also adds the following CLI command options
|
||||
One LED strip with up to 255 addressable LEDs can be configured to offer enhanced visualizations. The VU Meter visualizer includes a battery status indicator (see Battery). Currently only WS2812B LEDs are supported. Set the LED Strip hardware configuration, or the NVS led_vu_config syntax is
|
||||
```
|
||||
<playerid> led_visual [<mode>] [brightness(1-255)]
|
||||
Toggles or selects the visulaizer mode.
|
||||
The visualizer brighness can be controled using the optional <brighness> tag.
|
||||
|
||||
<playerid> dmx <R,G,B|R,G,B,R,G,B ... R,G,B> [<offset>]
|
||||
Sets the LED at position "offset" to any RGB color where "R"(red),"G"(green), and "B"(blue) are values from 0(off) to 255(max brightness).
|
||||
Add additional RGB values to the delimited string to set multiple LEDs.
|
||||
type=[WS2812],length=<n>,gpio=<dataPin>[,scale=<gain>]
|
||||
```
|
||||
where `<n>` is the number of LEDs in the strip (1..255). A `<scale>` gain value (percentage) can be added to enhance effect responses.
|
||||
|
||||
The latest LMS plugin update is required to set the visualizer mode and brightness in the ESP32 Settings page for the player, or a controllable display (see Extra/SqueezeESP32 menus). The plugin adds additional LMS CLI commands.
|
||||
|
||||
| Command | Notes |
|
||||
| -------------------------------------------------- | ----------- |
|
||||
| \<playerid\> led_visual \[\<mode\>\] \[\<brightness\>\] | Toggles or selects the visualizer "mode".<br />The visualizer brightness(0..255) can be controlled using the "brightness" tag. |
|
||||
| \<playerid\> dmx \<R,G,B,R,G,B, ... R,G,B\> \[\<offset\>\] | Sets the LED color starting at position "offset"<br /> with "R"(red),"G"(green),and "B"(blue) color sequences.<br />Add additional RGB values to the delimited string to set multiple LEDs.<br /> |
|
||||
|
||||
### Rotary Encoder
|
||||
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
|
||||
@@ -393,9 +404,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 +517,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 +567,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.
|
||||
|
||||
@@ -586,7 +637,11 @@ You can install IDF manually on Linux or Windows (using the Subsystem for Linux)
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
|
||||
|
||||
## Building SqueezeESP32
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
|
||||
```
|
||||
$ sudo pip3 install protobuf grpcio-tools
|
||||
```
|
||||
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
|
||||
|
||||
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
|
||||
|
||||
@@ -618,5 +673,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
|
||||
|
||||

|
||||
|
||||
# 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
BIN
Soft Power.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -705,6 +705,26 @@ esp_err_t i2s_stop(i2s_port_t i2s_num)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* When a panic occurs during playback, the I2S interface can produce a loud noise burst.
|
||||
* This code runs just before the system panic handler to "emergency stop" the I2S iterface
|
||||
* to prevent the noise burst from happening. Note that when this code is called the system
|
||||
* has already crashed, so no need to disable interrupts, acquire locks, or otherwise be nice.
|
||||
*
|
||||
* This code makes use of the linker --wrap feature to intercept the call to esp_panic_handler.
|
||||
*/
|
||||
|
||||
void __real_esp_panic_handler(void*);
|
||||
|
||||
void __wrap_esp_panic_handler (void* info) {
|
||||
esp_rom_printf("I2S abort!\r\n");
|
||||
|
||||
i2s_hal_stop_tx(&(p_i2s_obj[CONFIG_I2S_NUM]->hal));
|
||||
|
||||
/* Call the original panic handler function to finish processing this error */
|
||||
__real_esp_panic_handler(info);
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_ADC_DAC
|
||||
esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
* Copyright (C) 2011-2023 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
|
||||
|
||||
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
|
||||
* prepare to receive a new stream once the last Ogg page arrives for
|
||||
* the stream encapsulating the FLAC audio data. This can be used to
|
||||
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
|
||||
* beginning of a new stream.
|
||||
*
|
||||
* \note
|
||||
* This function has no effect with native FLAC decoding.
|
||||
*
|
||||
* \default \c false
|
||||
* \param decoder A decoder instance to set.
|
||||
* \param allow Whether to allow chained streams.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* \c false if the decoder is already initialized, else \c true.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
|
||||
|
||||
/** Set the "MD5 signature checking" flag. If \c true, the decoder will
|
||||
* compute the MD5 signature of the unencoded audio data while decoding
|
||||
* and compare it to the signature from the STREAMINFO block, if it
|
||||
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
|
||||
*/
|
||||
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
|
||||
|
||||
/** Get the "allow Ogg chaining" flag as described in
|
||||
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
|
||||
*
|
||||
* \param decoder A decoder instance to query.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* See above.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
|
||||
|
||||
/** Get the "MD5 signature checking" flag.
|
||||
* This is the value of the setting, not whether or not the decoder is
|
||||
* currently checking the MD5 (remember, it can be turned off automatically
|
||||
|
||||
Binary file not shown.
176
components/display/SH1122.c
Normal file
176
components/display/SH1122.c
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define PAGE_BLOCK 1024
|
||||
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "SH1122";
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
uint8_t PageSize;
|
||||
};
|
||||
|
||||
// Functions are not declared to minimize # of lines
|
||||
|
||||
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
|
||||
Device->WriteCommand( Device, 0x10 | (Start >> 4) );
|
||||
Device->WriteCommand( Device, 0x00 | (Start & 0x0f) );
|
||||
}
|
||||
|
||||
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
|
||||
Device->WriteCommand( Device, 0xB0 );
|
||||
Device->WriteCommand( Device, Start );
|
||||
}
|
||||
|
||||
static void Update( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
// RAM is by columns of 4 pixels ...
|
||||
SetColumnAddress( Device, 0, Device->Width / 4 - 1);
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
|
||||
bool dirty = false;
|
||||
|
||||
for (int r = 0, page = 0; r < Device->Height; r++) {
|
||||
// look for change and update shadow (cheap optimization = width always / by 2)
|
||||
for (int c = Device->Width / 2 / 2; --c >= 0;) {
|
||||
if (*optr != *iptr) {
|
||||
dirty = true;
|
||||
*optr = *iptr;
|
||||
}
|
||||
iptr++; optr++;
|
||||
}
|
||||
|
||||
// one line done, check for page boundary
|
||||
if (++page == Private->PageSize) {
|
||||
if (dirty) {
|
||||
SetRowAddress( Device, r - page + 1, r );
|
||||
if (Private->iRAM) {
|
||||
memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2);
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
page = 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
SetRowAddress( Device, 0, Device->Height - 1 );
|
||||
for (int r = 0; r < Device->Height; r += Private->PageSize) {
|
||||
if (Private->iRAM) {
|
||||
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
|
||||
if (Layout->HFlip) {
|
||||
Device->WriteCommand( Device, 0x40 + 0x20 );
|
||||
Device->WriteCommand( Device, 0xA1 );
|
||||
} else {
|
||||
Device->WriteCommand( Device, 0x40 + 0x00 );
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
}
|
||||
Device->WriteCommand( Device, Layout->VFlip ? 0xC8 : 0xC0 );
|
||||
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
Device->WriteCommand( Device, 0x81 );
|
||||
Device->WriteCommand( Device, Contrast );
|
||||
}
|
||||
|
||||
static bool Init( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
// find a page size that is not too small is an integer of height
|
||||
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
|
||||
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
Private->Shadowbuffer = malloc( Device->FramebufferSize );
|
||||
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
|
||||
#endif
|
||||
|
||||
// only use iRAM for SPI
|
||||
if (Device->IF == GDS_IF_SPI) {
|
||||
Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SH1122 page %u, iRAM %p", Private->PageSize, Private->iRAM);
|
||||
|
||||
// need to be off and disable display RAM
|
||||
Device->DisplayOff( Device );
|
||||
Device->WriteCommand( Device, 0xA5 );
|
||||
|
||||
// Display Offset
|
||||
Device->WriteCommand( Device, 0xD3 );
|
||||
Device->WriteCommand( Device, 0 );
|
||||
|
||||
// set flip modes
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
|
||||
// set Clocks => check value
|
||||
Device->WriteCommand( Device, 0xD5 );
|
||||
Device->WriteCommand( Device, ( 0x04 << 4 ) | 0x00 );
|
||||
|
||||
// MUX Ratio => fixed
|
||||
Device->WriteCommand( Device, 0xA8 );
|
||||
Device->WriteCommand( Device, Device->Height - 1);
|
||||
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
|
||||
// gone with the wind
|
||||
Device->WriteCommand( Device, 0xA4 );
|
||||
Device->DisplayOn( Device );
|
||||
Device->Update( Device );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct GDS_Device SH1122 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update, .Init = Init,
|
||||
.Mode = GDS_GRAYSCALE, .Depth = 4,
|
||||
.HighNibble = true,
|
||||
};
|
||||
|
||||
struct GDS_Device* SH1122_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!strcasestr(Driver, "SH1122")) return NULL;
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*Device = SH1122;
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -71,8 +71,8 @@ static void Update( struct GDS_Device* Device ) {
|
||||
if (dirty) {
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2);
|
||||
SetRowAddress( Device, r - page + 1, r );
|
||||
// need byte swapping
|
||||
for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
//memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
|
||||
Device->WriteCommand( Device, 0x5c );
|
||||
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
|
||||
dirty = false;
|
||||
@@ -84,14 +84,10 @@ static void Update( struct GDS_Device* Device ) {
|
||||
for (int r = 0; r < Device->Height; r += Private->PageSize) {
|
||||
SetRowAddress( Device, r, r + Private->PageSize - 1 );
|
||||
Device->WriteCommand( Device, 0x5c );
|
||||
if (Private->iRAM) {
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
|
||||
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
//memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
// need byte swapping
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
|
||||
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -54,6 +54,7 @@ struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], s
|
||||
}
|
||||
|
||||
void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return);
|
||||
bool commit = true;
|
||||
|
||||
if (full) {
|
||||
@@ -74,6 +75,7 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
|
||||
}
|
||||
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color ) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return);
|
||||
if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize );
|
||||
else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize );
|
||||
else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
|
||||
@@ -89,6 +91,7 @@ void GDS_Clear( struct GDS_Device* Device, int Color ) {
|
||||
}
|
||||
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return);
|
||||
// -1 means up to width/height
|
||||
if (x2 < 0) x2 = Device->Width - 1;
|
||||
if (y2 < 0) y2 = Device->Height - 1;
|
||||
@@ -109,7 +112,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
int c = x1;
|
||||
// for a row that is not on a boundary, no optimization possible
|
||||
while (r & 0x07 && r <= y2) {
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
// go fast if we have more than 8 lines to write
|
||||
@@ -117,7 +120,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
|
||||
r += 8;
|
||||
} else while (r <= y2) {
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
}
|
||||
@@ -133,10 +136,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
// try to do byte processing as much as possible
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
int c = x1;
|
||||
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
|
||||
if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
|
||||
int chunk = (x2 - c + 1) >> 1;
|
||||
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
|
||||
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
|
||||
if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
@@ -148,7 +151,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
} else {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
DrawPixelFast( Device, x, y, Color);
|
||||
Device->DrawPixelFast( Device, x, y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,11 +161,13 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
}
|
||||
|
||||
void GDS_Update( struct GDS_Device* Device ) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return);
|
||||
if (Device->Dirty) Device->Update( Device );
|
||||
Device->Dirty = false;
|
||||
}
|
||||
|
||||
bool GDS_Reset( struct GDS_Device* Device ) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return false);
|
||||
if ( Device->RSTPin >= 0 ) {
|
||||
gpio_set_level( Device->RSTPin, 0 );
|
||||
vTaskDelay( pdMS_TO_TICKS( 100 ) );
|
||||
@@ -171,10 +176,76 @@ bool GDS_Reset( struct GDS_Device* Device ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
* of the screen is the same as the width.
|
||||
* Dividing Y by 8 gives us which row the pixel is in but not
|
||||
* the bit position.
|
||||
*/
|
||||
Y>>= 3;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
|
||||
|
||||
if ( Color == GDS_COLOR_XOR ) {
|
||||
*FBOffset ^= BIT( YBit );
|
||||
} else {
|
||||
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
bool GDS_Init( struct GDS_Device* Device ) {
|
||||
|
||||
GDS_CHECK_FOR_DEVICE(Device,return false);
|
||||
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
|
||||
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
|
||||
|
||||
// set the proper DrawPixel function if not already set by driver
|
||||
if (!Device->DrawPixelFast) {
|
||||
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
|
||||
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
|
||||
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
|
||||
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
|
||||
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
|
||||
}
|
||||
|
||||
// allocate FB unless explicitely asked not to
|
||||
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
|
||||
@@ -208,6 +279,7 @@ bool GDS_Init( struct GDS_Device* Device ) {
|
||||
}
|
||||
|
||||
int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return -1);
|
||||
switch(Device->Mode) {
|
||||
case GDS_MONO: return Level;
|
||||
case GDS_GRAYSCALE: return Level >> (8 - Device->Depth);
|
||||
@@ -234,6 +306,7 @@ int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
|
||||
}
|
||||
|
||||
void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
GDS_CHECK_FOR_DEVICE(Device,return);
|
||||
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
|
||||
else if (Device->Backlight.Pin >= 0) {
|
||||
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
|
||||
@@ -242,12 +315,12 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
}
|
||||
}
|
||||
|
||||
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device->SetLayout) Device->SetLayout( Device, Layout ); }
|
||||
void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }
|
||||
void GDS_SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { if (Device && Device->SetLayout) Device->SetLayout( Device, Layout ); }
|
||||
void GDS_SetDirty( struct GDS_Device* Device ) { GDS_CHECK_FOR_DEVICE(Device,return); Device->Dirty = true; }
|
||||
int GDS_GetWidth( struct GDS_Device* Device ) { return Device ? Device->Width : 0; }
|
||||
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
|
||||
void GDS_SetTextWidth( struct GDS_Device* Device, int TextWidth ) { GDS_CHECK_FOR_DEVICE(Device,return); Device->TextWidth = Device && TextWidth && TextWidth < Device->Width ? TextWidth : Device->Width; }
|
||||
int GDS_GetHeight( struct GDS_Device* Device ) { return Device ? Device->Height : 0; }
|
||||
int GDS_GetDepth( struct GDS_Device* Device ) { return Device ? Device->Depth : 0; }
|
||||
int GDS_GetMode( struct GDS_Device* Device ) { return Device ? Device->Mode : 0; }
|
||||
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); }
|
||||
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); }
|
||||
void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device && Device->DisplayOn) Device->DisplayOn( Device ); }
|
||||
void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device && Device->DisplayOff) Device->DisplayOff( Device ); }
|
||||
@@ -52,5 +52,6 @@ int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level );
|
||||
void GDS_ClearExt( struct GDS_Device* Device, bool full, ...);
|
||||
void GDS_Clear( struct GDS_Device* Device, int Color );
|
||||
void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
|
||||
#define GDS_CHECK_FOR_DEVICE(dev,ret_act) if(!dev) ret_act
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,7 +45,7 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
DrawPixelFast( Device, X, Y, Color );
|
||||
Device->DrawPixelFast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
@@ -63,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= Device->Height) y = Device->Height - 1;
|
||||
|
||||
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
|
||||
for ( ; x < XEnd; x++ ) Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
|
||||
@@ -97,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; x < x1; x++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -126,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; y < y1; y++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -213,37 +213,65 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
iptr += Height;
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 4) {
|
||||
} else if (Device->Depth == 4) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width >> 1;
|
||||
|
||||
Height >>= 3;
|
||||
Color &= 0x0f;
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
|
||||
if (Device->HighNibble) {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
} else {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
@@ -320,14 +348,14 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
// don't know bitdepth, use brute-force solution
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = *Data++;
|
||||
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
if (++r == Height) { c++; r = 0; }
|
||||
}
|
||||
/* for better understanding, here is the mundane version
|
||||
|
||||
@@ -98,6 +98,7 @@ struct GDS_Device {
|
||||
uint16_t Width, TextWidth;
|
||||
uint16_t Height;
|
||||
uint8_t Depth, Mode;
|
||||
bool HighNibble;
|
||||
|
||||
uint8_t Alloc;
|
||||
uint8_t* Framebuffer;
|
||||
@@ -155,69 +156,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
return Result;
|
||||
}
|
||||
|
||||
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
* of the screen is the same as the width.
|
||||
* Dividing Y by 8 gives us which row the pixel is in but not
|
||||
* the bit position.
|
||||
*/
|
||||
Y>>= 3;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
|
||||
|
||||
if ( Color == GDS_COLOR_XOR ) {
|
||||
*FBOffset ^= BIT( YBit );
|
||||
} else {
|
||||
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
|
||||
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
|
||||
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
|
||||
for (int y = Y_min; y < Y_max; y++)
|
||||
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
}
|
||||
|
||||
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );
|
||||
|
||||
@@ -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"
|
||||
@@ -62,6 +63,7 @@ static EXT_RAM_ATTR struct {
|
||||
} displayer;
|
||||
|
||||
static const char *known_drivers[] = {"SH1106",
|
||||
"SH1122",
|
||||
"SSD1306",
|
||||
"SSD1322",
|
||||
"SSD1326",
|
||||
@@ -73,11 +75,13 @@ 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;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
|
||||
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
@@ -174,11 +178,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
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -293,11 +293,8 @@ bool led_strip_init(struct led_strip_t *led_strip)
|
||||
static EXT_RAM_ATTR StackType_t xStack[LED_STRIP_TASK_SIZE] __attribute__ ((aligned (4)));
|
||||
|
||||
if ((led_strip == NULL) ||
|
||||
(led_strip->rmt_channel >= RMT_CHANNEL_MAX) ||
|
||||
(led_strip->gpio > GPIO_NUM_33) ||
|
||||
(led_strip->led_strip_working == NULL) ||
|
||||
(led_strip->led_strip_showing == NULL) ||
|
||||
(led_strip->led_strip_length == 0) ||
|
||||
(led_strip->access_semaphore == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,19 @@
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "globdefs.h"
|
||||
#include "monitor.h"
|
||||
#include "led_strip.h"
|
||||
#include "platform_config.h"
|
||||
#include "services.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 +41,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 +55,8 @@ static EXT_RAM_ATTR struct {
|
||||
int vu_length;
|
||||
int vu_start_l;
|
||||
int vu_start_r;
|
||||
int vu_odd;
|
||||
int vu_status;
|
||||
int vu_scale;
|
||||
} strip;
|
||||
|
||||
static int led_addr(int pos ) {
|
||||
@@ -55,54 +65,73 @@ 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);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Suspend.
|
||||
*
|
||||
*/
|
||||
static void led_vu_sleep(void) {
|
||||
led_vu_clear(led_display);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Initialize the led vu strip if configured.
|
||||
*
|
||||
*/
|
||||
void led_vu_init()
|
||||
{
|
||||
char* p;
|
||||
char* config = config_alloc_get_str("led_vu_config", NULL, "N/A");
|
||||
|
||||
// Initialize led VU strip
|
||||
char* drivername = strcasestr(config, "WS2812");
|
||||
|
||||
if ((p = strcasestr(config, "length")) != NULL) {
|
||||
strip.length = atoi(strchr(p, '=') + 1);
|
||||
} // else 0
|
||||
if ((p = strcasestr(config, "gpio")) != NULL) {
|
||||
strip.gpio = atoi(strchr(p, '=') + 1);
|
||||
} else {
|
||||
strip.gpio = LED_VU_DEFAULT_GPIO;
|
||||
}
|
||||
PARSE_PARAM(config, "length",'=', strip.length);
|
||||
PARSE_PARAM(config, "gpio",'=', strip.gpio);
|
||||
// check for valid configuration
|
||||
if (!drivername || !strip.gpio) {
|
||||
if (!strip.gpio) {
|
||||
ESP_LOGI(TAG, "led_vu configuration invalid");
|
||||
goto done;
|
||||
}
|
||||
strip.vu_scale = 100;
|
||||
PARSE_PARAM(config, "scale",'=',strip.vu_scale);
|
||||
|
||||
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 scale:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status, strip.vu_scale);
|
||||
|
||||
// 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;
|
||||
@@ -111,6 +140,8 @@ void led_vu_init()
|
||||
// reserver max memory for remote management systems
|
||||
rmt_set_mem_block_num(led_strip_config.rmt_channel, 7);
|
||||
|
||||
services_sleep_setsuspend(led_vu_sleep);
|
||||
|
||||
led_vu_clear(led_display);
|
||||
|
||||
done:
|
||||
@@ -130,6 +161,14 @@ uint16_t led_vu_string_length() {
|
||||
return (uint16_t)strip.length;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Returns a user defined scale (percent)
|
||||
*/
|
||||
uint16_t led_vu_scale() {
|
||||
if (!led_display) return 0;
|
||||
return (uint16_t)strip.vu_scale;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Turns all LEDs off (Black)
|
||||
*/
|
||||
@@ -295,7 +334,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 +399,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
extern struct led_strip_t* led_display;
|
||||
|
||||
uint16_t led_vu_string_length();
|
||||
uint16_t led_vu_scale();
|
||||
void led_vu_progress_bar(int pct, int bright);
|
||||
void led_vu_display(int vu_l, int vu_r, int bright, bool comet);
|
||||
void led_vu_spin_dial(int gain, int rate, int speed, bool comet);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@ bool config_set_group_bit(int bit_num,bool flag){
|
||||
return result;
|
||||
}
|
||||
|
||||
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size) {
|
||||
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size) {
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config");
|
||||
return;
|
||||
|
||||
@@ -56,7 +56,7 @@ cJSON * config_alloc_get_cjson(const char *key);
|
||||
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value);
|
||||
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value);
|
||||
void config_delete_key(const char *key);
|
||||
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
|
||||
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size);
|
||||
void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
|
||||
bool wait_for_commit();
|
||||
char * config_alloc_get_json(bool bFormatted);
|
||||
|
||||
@@ -43,11 +43,15 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
|
||||
extern void register_audio_config(void);
|
||||
extern void register_rotary_config(void);
|
||||
extern void register_ledvu_config(void);
|
||||
extern void register_nvs();
|
||||
|
||||
void register_optional_cmd(void) {
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
register_rotary_config();
|
||||
register_ledvu_config();
|
||||
#endif
|
||||
register_audio_config();
|
||||
register_ledvu_config();
|
||||
register_nvs();
|
||||
}
|
||||
|
||||
extern int squeezelite_main(int argc, char **argv);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -62,10 +62,13 @@ static void register_free();
|
||||
static void register_setdevicename();
|
||||
static void register_heap();
|
||||
static void register_dump_heap();
|
||||
static void register_abort();
|
||||
static void register_version();
|
||||
static void register_restart();
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
static void register_deep_sleep();
|
||||
static void register_light_sleep();
|
||||
#endif
|
||||
static void register_factory_boot();
|
||||
static void register_restart_ota();
|
||||
static void register_set_services();
|
||||
@@ -73,24 +76,43 @@ static void register_set_services();
|
||||
static void register_tasks();
|
||||
#endif
|
||||
extern BaseType_t network_manager_task;
|
||||
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size){
|
||||
FILE *f = open_memstream(buf, buf_size);
|
||||
if (f == NULL) {
|
||||
cmd_send_messaging(cmdname,MESSAGING_ERROR,"Unable to open memory stream.");
|
||||
}
|
||||
return f;
|
||||
}
|
||||
void register_system()
|
||||
{
|
||||
register_free();
|
||||
|
||||
register_setdevicename();
|
||||
register_set_services();
|
||||
register_free();
|
||||
register_heap();
|
||||
register_dump_heap();
|
||||
register_setdevicename();
|
||||
register_abort();
|
||||
register_version();
|
||||
register_restart();
|
||||
register_deep_sleep();
|
||||
register_light_sleep();
|
||||
register_factory_boot();
|
||||
register_restart_ota();
|
||||
#if WITH_TASKS_INFO
|
||||
register_tasks();
|
||||
#endif
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
register_deep_sleep();
|
||||
register_light_sleep();
|
||||
#endif
|
||||
}
|
||||
void simple_restart()
|
||||
{
|
||||
log_send_messaging(MESSAGING_WARNING,"Rebooting.");
|
||||
if(!wait_for_commit()){
|
||||
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
|
||||
}
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
/* 'version' command */
|
||||
static int get_version(int argc, char **argv)
|
||||
{
|
||||
@@ -124,40 +146,48 @@ static void register_version()
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/* 'abort' command */
|
||||
static int cmd_abort(int argc, char **argv)
|
||||
{
|
||||
cmd_send_messaging(argv[0],MESSAGING_INFO,"ABORT!\r\n");
|
||||
|
||||
abort();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_abort()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "abort",
|
||||
.help = "Crash now!",
|
||||
.hint = NULL,
|
||||
.func = &cmd_abort,
|
||||
};
|
||||
cmd_to_json(&cmd);
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
{
|
||||
if(is_recovery_running){
|
||||
if(partition_subtype ==ESP_PARTITION_SUBTYPE_APP_FACTORY){
|
||||
log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
|
||||
if(!wait_for_commit()){
|
||||
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
|
||||
}
|
||||
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
return ESP_OK;
|
||||
// log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
|
||||
simple_restart();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(partition_subtype !=ESP_PARTITION_SUBTYPE_APP_FACTORY){
|
||||
log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
|
||||
if(!wait_for_commit()){
|
||||
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
|
||||
}
|
||||
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
return ESP_OK;
|
||||
// log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
|
||||
simple_restart();
|
||||
}
|
||||
}
|
||||
esp_err_t err = ESP_OK;
|
||||
bool bFound=false;
|
||||
log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
|
||||
// log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
|
||||
const esp_partition_t *partition;
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
|
||||
|
||||
if(it == NULL){
|
||||
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Cannot iterate through partitions");
|
||||
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Partitions error");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -166,15 +196,11 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
ESP_LOGD(TAG, "Releasing partition iterator");
|
||||
esp_partition_iterator_release(it);
|
||||
if(partition != NULL){
|
||||
log_send_messaging(MESSAGING_INFO, "Found application partition %s sub type %u", partition->label,partition_subtype);
|
||||
log_send_messaging(MESSAGING_INFO, "Rebooting to %s", partition->label);
|
||||
err=esp_ota_set_boot_partition(partition);
|
||||
if(err!=ESP_OK){
|
||||
bFound=false;
|
||||
log_send_messaging(MESSAGING_ERROR,"Unable to select partition for reboot: %s",esp_err_to_name(err));
|
||||
}
|
||||
else{
|
||||
bFound=true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,13 +209,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
}
|
||||
ESP_LOGD(TAG, "Yielding to other processes");
|
||||
taskYIELD();
|
||||
if(bFound) {
|
||||
if(!wait_for_commit()){
|
||||
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration changes. ");
|
||||
}
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
}
|
||||
simple_restart();
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@@ -197,46 +217,31 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
log_send_messaging(MESSAGING_WARNING, "\n\nPerforming a simple restart to the currently active partition.");
|
||||
if(!wait_for_commit()){
|
||||
cmd_send_messaging(argv[0],MESSAGING_WARNING,"Unable to commit configuration. ");
|
||||
}
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
simple_restart();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void simple_restart()
|
||||
{
|
||||
log_send_messaging(MESSAGING_WARNING,"System reboot requested.");
|
||||
if(!wait_for_commit()){
|
||||
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
|
||||
}
|
||||
|
||||
|
||||
vTaskDelay(750/ portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
esp_err_t guided_restart_ota(){
|
||||
log_send_messaging(MESSAGING_WARNING,"System reboot to Application requested");
|
||||
log_send_messaging(MESSAGING_WARNING,"Booting to Squeezelite");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
esp_err_t guided_factory(){
|
||||
log_send_messaging(MESSAGING_WARNING,"System reboot to recovery requested");
|
||||
log_send_messaging(MESSAGING_WARNING,"Booting to recovery");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
static int restart_factory(int argc, char **argv)
|
||||
{
|
||||
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into recovery");
|
||||
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Recovery");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
return 0; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
static int restart_ota(int argc, char **argv)
|
||||
{
|
||||
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into ota app 0");
|
||||
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Squeezelite");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
|
||||
return 0; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
@@ -248,7 +253,9 @@ static void register_restart()
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
cmd_to_json(&cmd);
|
||||
#endif
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
static void register_restart_ota()
|
||||
@@ -259,7 +266,9 @@ static void register_restart_ota()
|
||||
.hint = NULL,
|
||||
.func = &restart_ota,
|
||||
};
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
cmd_to_json(&cmd);
|
||||
#endif
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
@@ -271,7 +280,9 @@ static void register_factory_boot()
|
||||
.hint = NULL,
|
||||
.func = &restart_factory,
|
||||
};
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
cmd_to_json(&cmd);
|
||||
#endif
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
/** 'free' command prints available heap memory */
|
||||
@@ -287,11 +298,14 @@ static void register_free()
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "free",
|
||||
.help = "Get the current size of free heap memory",
|
||||
.help = "Get free heap memory",
|
||||
.hint = NULL,
|
||||
.func = &free_mem,
|
||||
};
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
cmd_to_json(&cmd);
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
static int dump_heap(int argc, char **argv)
|
||||
@@ -303,16 +317,16 @@ static int dump_heap(int argc, char **argv)
|
||||
/* 'heap' command prints minumum heap size */
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
|
||||
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
|
||||
heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
// ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
|
||||
// heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
// heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
|
||||
// heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
// heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
|
||||
// heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
|
||||
// heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
|
||||
cmd_send_messaging(argv[0],MESSAGING_INFO,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
|
||||
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
@@ -457,9 +471,8 @@ static int setdevicename(int argc, char **argv)
|
||||
|
||||
char *buf = NULL;
|
||||
size_t buf_size = 0;
|
||||
FILE *f = open_memstream(&buf, &buf_size);
|
||||
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
|
||||
if (f == NULL) {
|
||||
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
|
||||
return 1;
|
||||
}
|
||||
nerrors+=setnamevar("a2dp_dev_name", f, name);
|
||||
@@ -488,11 +501,13 @@ static void register_heap()
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory found during execution",
|
||||
.help = "Get minimum size of free heap memory",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
cmd_to_json(&heap_cmd);
|
||||
#endif
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
|
||||
|
||||
}
|
||||
@@ -521,6 +536,7 @@ static void register_setdevicename()
|
||||
.func = &setdevicename,
|
||||
.argtable = &name_args
|
||||
};
|
||||
|
||||
cmd_to_json_with_cb(&set_name,&setdevicename_cb);
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&set_name));
|
||||
}
|
||||
@@ -561,7 +577,7 @@ static void register_tasks()
|
||||
|
||||
|
||||
/** 'deep_sleep' command puts the chip into deep sleep mode */
|
||||
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
@@ -618,15 +634,15 @@ static void register_deep_sleep()
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "deep_sleep",
|
||||
.help = "Enter deep sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"If no wakeup option is specified, will sleep indefinitely.",
|
||||
.help = "Enter deep sleep mode. ",
|
||||
.hint = NULL,
|
||||
.func = &deep_sleep,
|
||||
.argtable = &deep_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
#endif
|
||||
|
||||
static int enable_disable(FILE * f,char * nvs_name, struct arg_lit *arg){
|
||||
esp_err_t err = config_set_value(NVS_TYPE_STR, nvs_name, arg->count>0?"Y":"N");
|
||||
const char * name = arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary;
|
||||
@@ -649,9 +665,8 @@ static int do_set_services(int argc, char **argv)
|
||||
}
|
||||
char *buf = NULL;
|
||||
size_t buf_size = 0;
|
||||
FILE *f = open_memstream(&buf, &buf_size);
|
||||
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
|
||||
if (f == NULL) {
|
||||
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -674,7 +689,7 @@ static int do_set_services(int argc, char **argv)
|
||||
|
||||
if(err!=ESP_OK){
|
||||
nerrors++;
|
||||
fprintf(f,"Error setting telnet service to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
|
||||
fprintf(f,"Error setting telnet to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
fprintf(f,"Telnet service changed to %s\n",set_services_args.telnet->sval[0]);
|
||||
@@ -706,7 +721,6 @@ cJSON * set_services_cb(){
|
||||
#if WITH_TASKS_INFO
|
||||
console_set_bool_parameter(values,"stats",set_services_args.stats);
|
||||
#endif
|
||||
|
||||
if ((p = config_alloc_get(NVS_TYPE_STR, "telnet_enable")) != NULL) {
|
||||
if(strcasestr("YX",p)!=NULL){
|
||||
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Telnet Only");
|
||||
@@ -717,7 +731,6 @@ cJSON * set_services_cb(){
|
||||
else {
|
||||
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled");
|
||||
}
|
||||
|
||||
FREE_AND_NULL(p);
|
||||
}
|
||||
|
||||
@@ -745,6 +758,8 @@ static void register_set_services(){
|
||||
cmd_to_json_with_cb(&cmd,&set_services_cb);
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
#if CONFIG_WITH_CONFIG_UI
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
@@ -836,4 +851,4 @@ static void register_light_sleep()
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,6 +17,7 @@ void register_system();
|
||||
esp_err_t guided_factory();
|
||||
esp_err_t guided_restart_ota();
|
||||
void simple_restart();
|
||||
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -204,8 +204,10 @@ void register_wifi_join()
|
||||
|
||||
void register_wifi()
|
||||
{
|
||||
#ifdef WIFI_CMDLINE
|
||||
register_wifi_join();
|
||||
if(bypass_network_manager){
|
||||
initialise_wifi();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded aud
|
||||
u16_t len;
|
||||
u8_t ready;
|
||||
u8_t allocated;
|
||||
u8_t missed;
|
||||
} abuf_t;
|
||||
|
||||
typedef struct rtp_s {
|
||||
@@ -126,11 +127,6 @@ typedef struct rtp_s {
|
||||
u32_t rtp, time;
|
||||
u8_t status;
|
||||
} synchro;
|
||||
struct {
|
||||
u32_t time;
|
||||
seq_t seqno;
|
||||
u32_t rtptime;
|
||||
} record;
|
||||
int latency; // rtp hold depth in samples
|
||||
u32_t resent_req, resent_rec; // total resent + recovered frames
|
||||
u32_t silent_frames; // total silence frames
|
||||
@@ -147,8 +143,8 @@ typedef struct rtp_s {
|
||||
#endif
|
||||
|
||||
struct alac_codec_s *alac_codec;
|
||||
int flush_seqno;
|
||||
bool playing;
|
||||
int first_seqno;
|
||||
enum { RTP_WAIT, RTP_STREAM, RTP_PLAY } state;
|
||||
int stalled;
|
||||
raop_data_cb_t data_cb;
|
||||
raop_cmd_cb_t cmd_cb;
|
||||
@@ -231,7 +227,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
|
||||
ctx->rtp_host.sin_family = AF_INET;
|
||||
ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
|
||||
pthread_mutex_init(&ctx->ab_mutex, 0);
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->first_seqno = -1;
|
||||
ctx->latency = latency;
|
||||
ctx->ab_read = ctx->ab_write;
|
||||
|
||||
@@ -339,24 +335,23 @@ void rtp_end(rtp_t *ctx)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
|
||||
{
|
||||
bool rc = true;
|
||||
u32_t now = gettime_ms();
|
||||
{
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
// always store flush seqno as we only want stricly above it, even when equal to RECORD
|
||||
ctx->first_seqno = seqno;
|
||||
bool flushed = false;
|
||||
|
||||
if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
|
||||
rc = false;
|
||||
LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
|
||||
} else {
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->playing = false;
|
||||
ctx->flush_seqno = seqno;
|
||||
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
// no need to stop playing if recent or equal to record - but first_seqno is needed
|
||||
if (ctx->state == RTP_PLAY) {
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->state = RTP_WAIT;
|
||||
flushed = true;
|
||||
LOG_INFO("[%p]: FLUSH packets below %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
|
||||
|
||||
return rc;
|
||||
|
||||
if (!exit_locked || !flushed) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return flushed;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -367,11 +362,9 @@ void rtp_flush_release(rtp_t *ctx) {
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
|
||||
ctx->record.seqno = seqno;
|
||||
ctx->record.rtptime = rtptime;
|
||||
ctx->record.time = gettime_ms();
|
||||
|
||||
LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
|
||||
ctx->first_seqno = (seqno || rtptime) ? seqno : -1;
|
||||
ctx->state = RTP_WAIT;
|
||||
LOG_INFO("[%p]: record %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -442,29 +435,54 @@ static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outs
|
||||
/*---------------------------------------------------------------------------*/
|
||||
static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
|
||||
abuf_t *abuf = NULL;
|
||||
u32_t playtime;
|
||||
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
/* if we have received a RECORD with a seqno, then this is the first allowed rtp sequence number
|
||||
* and we are in RTP_WAIT state. If seqno was 0, then we are waiting for a flush that will tell
|
||||
* us what should be our first allowed packet but we must accept everything, wait and clean when
|
||||
* we the it arrives. This means that first packet moves us to RTP_STREAM state where we accept
|
||||
* frames but wait for the FLUSH. If this was a FLUSH while playing, then we are also in RTP_WAIT
|
||||
* state but we do have an allowed seqno and we should not accept any frame before we have it */
|
||||
|
||||
if (!ctx->playing) {
|
||||
if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
|
||||
(ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
|
||||
ctx->ab_write = seqno-1;
|
||||
ctx->ab_read = seqno;
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->playing = true;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
// if we have a pending first seqno and we are below, always ignore it
|
||||
if (ctx->first_seqno != -1 && seq_order(seqno, ctx->first_seqno)) {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->state == RTP_WAIT) {
|
||||
ctx->ab_write = seqno - 1;
|
||||
ctx->ab_read = ctx->ab_write + 1;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
if (ctx->first_seqno != -1) {
|
||||
LOG_INFO("[%p]: 1st accepted packet:%d, now playing", ctx, seqno);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
ctx->state = RTP_STREAM;
|
||||
LOG_INFO("[%p]: 1st accepted packet:%hu, waiting for FLUSH", ctx, seqno);
|
||||
}
|
||||
} else if (ctx->state == RTP_STREAM && ctx->first_seqno != -1 && seq_order(ctx->first_seqno, seqno + 1)) {
|
||||
// now we're talking, but first discard all packets with a seqno below first_seqno AND not ready
|
||||
while (seq_order(ctx->ab_read, ctx->first_seqno) ||
|
||||
!ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready) {
|
||||
ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = false;
|
||||
ctx->ab_read++;
|
||||
}
|
||||
LOG_INFO("[%p]: done waiting for FLUSH with packet:%d, now playing starting:%hu", ctx, seqno, ctx->ab_read);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
|
||||
if (seqno == (u16_t) (ctx->ab_write+1)) {
|
||||
// expected packet
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else if (seq_order(ctx->ab_write, seqno)) {
|
||||
@@ -475,7 +493,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
ctx->ab_read = seqno;
|
||||
} else {
|
||||
// request re-send missed frames and evaluate resent date as a whole *after*
|
||||
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
if (ctx->state == RTP_PLAY) rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
|
||||
// resend date is after all requests have been sent
|
||||
u32_t now = gettime_ms();
|
||||
@@ -488,16 +506,15 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
} else if (seq_order(ctx->ab_read, seqno + 1)) {
|
||||
// recovered packet, not yet sent
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->resent_rec++;
|
||||
LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else {
|
||||
// too late
|
||||
LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
// too late
|
||||
if (abuf->missed) LOG_INFO("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
abuf = NULL;
|
||||
}
|
||||
|
||||
if (ctx->in_frames++ > 1000) {
|
||||
@@ -508,6 +525,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
if (abuf) {
|
||||
alac_decode(ctx, abuf->data, data, len, &abuf->len);
|
||||
abuf->ready = 1;
|
||||
abuf->missed = 0;
|
||||
// this is the local rtptime when this frame is expected to play
|
||||
abuf->rtptime = rtptime;
|
||||
buffer_push_packet(ctx);
|
||||
@@ -528,7 +546,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
|
||||
|
||||
// not ready to play yet
|
||||
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
if (ctx->state != RTP_PLAY || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
|
||||
// there is always at least one frame in the buffer
|
||||
do {
|
||||
@@ -551,6 +569,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
|
||||
ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
|
||||
ctx->silent_frames++;
|
||||
curframe->missed = 1;
|
||||
}
|
||||
} else if (curframe->ready) {
|
||||
ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
|
||||
@@ -572,17 +591,25 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
}
|
||||
|
||||
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
|
||||
|
||||
// try to request resend missing packet in order, explore up to 32 frames
|
||||
for (int step = max((ctx->ab_write - ctx->ab_read + 1) / 32, 1),
|
||||
i = 0, first = 0;
|
||||
seq_order(ctx->ab_read + i, ctx->ab_write); i += step) {
|
||||
|
||||
abuf_t* frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
|
||||
|
||||
// each missing packet will be requested up to (latency_frames / 16) times
|
||||
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
|
||||
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
|
||||
if (!frame->ready && now - frame->last_resend > RESEND_TO) {
|
||||
// stop if one fails
|
||||
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break;
|
||||
frame->last_resend = now;
|
||||
}
|
||||
}
|
||||
|
||||
// stop when we reach a ready frame or a recent pending resend
|
||||
if (first && (frame->ready || now - frame->last_resend <= RESEND_TO)) {
|
||||
if (!rtp_request_resend(ctx, first, ctx->ab_read + i - 1)) break;
|
||||
first = 0;
|
||||
i += step - 1;
|
||||
} else if (!frame->ready && now - frame->last_resend > RESEND_TO) {
|
||||
if (!first) first = ctx->ab_read + i;
|
||||
frame->last_resend = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
@@ -329,7 +329,7 @@ esp_err_t config_ledvu_set(ledvu_struct_t * config){
|
||||
esp_err_t err=ESP_OK;
|
||||
char * config_buffer=malloc_init_external(buffer_size);
|
||||
if(config_buffer) {
|
||||
snprintf(config_buffer,buffer_size,"%s,length=%i,gpio=%i",config->type, config->length, config->gpio);
|
||||
snprintf(config_buffer,buffer_size,"%s,length=%i,gpio=%i,scale=%i",config->type, config->length, config->gpio, config->scale);
|
||||
log_send_messaging(MESSAGING_INFO,"Updating ledvu configuration to %s",config_buffer);
|
||||
err = config_set_value(NVS_TYPE_STR, "led_vu_config", config_buffer);
|
||||
if(err!=ESP_OK){
|
||||
@@ -761,14 +761,13 @@ const rotary_struct_t * config_rotary_get() {
|
||||
*/
|
||||
const ledvu_struct_t * config_ledvu_get() {
|
||||
|
||||
static ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0};
|
||||
static ledvu_struct_t ledvu={ .type = "WS2812", .gpio = -1, .length = 0, .scale= 100 };
|
||||
char *config = config_alloc_get_default(NVS_TYPE_STR, "led_vu_config", NULL, 0);
|
||||
if (config && *config) {
|
||||
char *p;
|
||||
|
||||
// ToDo: Add code for future support of alternate led types
|
||||
if ((p = strcasestr(config, "gpio")) != NULL) ledvu.gpio = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "length")) != NULL) ledvu.length = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM_STR(config, "type", '=', ledvu.type, 15);
|
||||
PARSE_PARAM(config, "gpio", '=', ledvu.gpio);
|
||||
PARSE_PARAM(config, "length", '=', ledvu.length);
|
||||
PARSE_PARAM(config, "scale", '=', ledvu.scale);
|
||||
free(config);
|
||||
}
|
||||
return &ledvu;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
extern const char *i2c_name_type;
|
||||
extern const char *spi_name_type;
|
||||
|
||||
@@ -89,6 +89,7 @@ typedef struct {
|
||||
char type[16];
|
||||
int length;
|
||||
int gpio;
|
||||
int scale;
|
||||
} ledvu_struct_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -83,6 +83,10 @@ static void mcp23s17_set_direction(gpio_exp_t* self);
|
||||
static uint32_t mcp23s17_read(gpio_exp_t* self);
|
||||
static void mcp23s17_write(gpio_exp_t* self);
|
||||
|
||||
static void aw9523_set_direction(gpio_exp_t* self);
|
||||
static uint32_t aw9523_read(gpio_exp_t* self);
|
||||
static void aw9523_write(gpio_exp_t* self);
|
||||
|
||||
static void service_handler(void *arg);
|
||||
static void debounce_handler( TimerHandle_t xTimer );
|
||||
|
||||
@@ -130,6 +134,11 @@ static const struct gpio_exp_model_s {
|
||||
.set_pull_mode = mcp23s17_set_pull_mode,
|
||||
.read = mcp23s17_read,
|
||||
.write = mcp23s17_write, },
|
||||
{ .model = "aw9523",
|
||||
.trigger = GPIO_INTR_LOW_LEVEL,
|
||||
.set_direction = aw9523_set_direction,
|
||||
.read = aw9523_read,
|
||||
.write = aw9523_write, },
|
||||
};
|
||||
|
||||
static EXT_RAM_ATTR uint8_t n_expanders;
|
||||
@@ -671,6 +680,24 @@ static void mcp23s17_write(gpio_exp_t* self) {
|
||||
spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* AW9523 family : direction, read and write
|
||||
*/
|
||||
static void aw9523_set_direction(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x06, ~self->r_mask, 2);
|
||||
}
|
||||
|
||||
static uint32_t aw9523_read(gpio_exp_t* self) {
|
||||
// Reading both registers in one go does not seem to reset IRQ correctly
|
||||
uint8_t port1 = i2c_read(self->phy.port, self->phy.addr, 0x00, 1);
|
||||
return (i2c_read(self->phy.port, self->phy.addr, 0x01, 1) << 8) | port1;
|
||||
}
|
||||
|
||||
static void aw9523_write(gpio_exp_t* self) {
|
||||
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
|
||||
}
|
||||
|
||||
/***************************************************************************************
|
||||
I2C low level
|
||||
***************************************************************************************/
|
||||
@@ -793,4 +820,4 @@ static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg,
|
||||
free(transaction);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -62,7 +62,7 @@ static void task_stats( cJSON* top ) {
|
||||
current.n = uxTaskGetSystemState( current.tasks, current.n, ¤t.total );
|
||||
cJSON_AddNumberToObject(top,"ntasks",current.n);
|
||||
|
||||
char scratch[SCRATCH_SIZE] = { };
|
||||
char scratch[SCRATCH_SIZE] = {0};
|
||||
|
||||
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
||||
#pragma message("Compiled with runtime stats")
|
||||
@@ -143,11 +143,13 @@ static void monitor_trace(uint32_t now) {
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
|
||||
|
||||
task_stats(top);
|
||||
|
||||
char * top_a= cJSON_PrintUnformatted(top);
|
||||
if(top_a){
|
||||
messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
|
||||
FREE_AND_NULL(top_a);
|
||||
}
|
||||
|
||||
cJSON_Delete(top);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -92,10 +361,10 @@ void services_init(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// set potential power GPIO on chip first in case expanders are power using these
|
||||
// set potential power GPIO on chip first in case expanders are powered 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,44 +374,44 @@ 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 );
|
||||
spi_bus_initialize( spi_system_host, spi_config, SPI_DMA_CH_AUTO );
|
||||
if (spi_system_dc_gpio != -1) {
|
||||
gpio_reset_pin(spi_system_dc_gpio);
|
||||
gpio_set_direction( spi_system_dc_gpio, GPIO_MODE_OUTPUT );
|
||||
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();
|
||||
|
||||
17
components/services/services.h
Normal file
17
components/services/services.h
Normal 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);
|
||||
@@ -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,12 @@ 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;
|
||||
std::atomic<bool> flushed = false, notify = true;
|
||||
|
||||
int startOffset, volume = 0, bitrate = 160;
|
||||
httpd_handle_t serverHandle;
|
||||
@@ -50,6 +61,7 @@ private:
|
||||
cspot_cmd_cb_t cmdHandler;
|
||||
cspot_data_cb_t dataHandler;
|
||||
std::string lastTrackId;
|
||||
cspot::TrackInfo trackInfo;
|
||||
|
||||
std::shared_ptr<cspot::LoginBlob> blob;
|
||||
std::unique_ptr<cspot::SpircHandler> spirc;
|
||||
@@ -57,6 +69,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 +92,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 +122,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" {
|
||||
@@ -179,11 +208,13 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
||||
trackStatus = TRACK_INIT;
|
||||
// memorize position for when track's beginning will be detected
|
||||
startOffset = std::get<int>(event->data);
|
||||
notify = !flushed;
|
||||
flushed = false;
|
||||
// Spotify servers do not send volume at connection
|
||||
spirc->setRemoteVolume(volume);
|
||||
|
||||
cmdHandler(CSPOT_START, 44100);
|
||||
CSPOT_LOG(info, "(re)start playing");
|
||||
CSPOT_LOG(info, "(re)start playing at %d", startOffset);
|
||||
break;
|
||||
}
|
||||
case cspot::SpircHandler::EventType::PLAY_PAUSE: {
|
||||
@@ -192,23 +223,20 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
|
||||
break;
|
||||
}
|
||||
case cspot::SpircHandler::EventType::TRACK_INFO: {
|
||||
auto trackInfo = std::get<cspot::TrackInfo>(event->data);
|
||||
cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
|
||||
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
|
||||
spirc->updatePositionMs(startOffset);
|
||||
startOffset = 0;
|
||||
trackInfo = std::get<cspot::TrackInfo>(event->data);
|
||||
break;
|
||||
}
|
||||
case cspot::SpircHandler::EventType::FLUSH:
|
||||
flushed = true;
|
||||
__attribute__ ((fallthrough));
|
||||
case cspot::SpircHandler::EventType::NEXT:
|
||||
case cspot::SpircHandler::EventType::PREV:
|
||||
case cspot::SpircHandler::EventType::FLUSH: {
|
||||
// FLUSH is sent when there is no next, just clean everything
|
||||
case cspot::SpircHandler::EventType::PREV: {
|
||||
cmdHandler(CSPOT_FLUSH);
|
||||
break;
|
||||
}
|
||||
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 +294,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 +314,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 +364,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 +404,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
|
||||
@@ -357,8 +413,13 @@ void cspotPlayer::runTask() {
|
||||
uint32_t started;
|
||||
cmdHandler(CSPOT_QUERY_STARTED, &started);
|
||||
if (started) {
|
||||
CSPOT_LOG(info, "next track's audio has reached DAC");
|
||||
spirc->notifyAudioReachedPlayback();
|
||||
CSPOT_LOG(info, "next track's audio has reached DAC (offset %d)", startOffset);
|
||||
if (notify) spirc->notifyAudioReachedPlayback();
|
||||
else notify = true;
|
||||
cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
|
||||
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
|
||||
spirc->updatePositionMs(startOffset);
|
||||
startOffset = 0;
|
||||
trackStatus = TRACK_STREAM;
|
||||
}
|
||||
} else if (trackStatus == TRACK_END) {
|
||||
@@ -369,26 +430,35 @@ void cspotPlayer::runTask() {
|
||||
CSPOT_LOG(info, "last track finished");
|
||||
trackStatus = TRACK_INIT;
|
||||
cmdHandler(CSPOT_STOP);
|
||||
spirc->setPause(true);
|
||||
spirc->notifyAudioEnded();
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,6 +467,7 @@ void cspotPlayer::runTask() {
|
||||
*/
|
||||
struct cspot_s* cspot_create(const char *name, httpd_handle_t server, int port, cspot_cmd_cb_t cmd_cb, cspot_data_cb_t data_cb) {
|
||||
bell::setDefaultLogger();
|
||||
bell::enableTimestampLogging(true);
|
||||
player = new cspotPlayer(name, server, port, cmd_cb, data_cb);
|
||||
player->startTask();
|
||||
return (cspot_s*) player;
|
||||
|
||||
@@ -22482,9 +22482,13 @@ mg_init_library(unsigned features)
|
||||
file_mutex_init =
|
||||
pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr);
|
||||
if (file_mutex_init == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
/* Start WinSock */
|
||||
WSADATA data;
|
||||
failed = wsa = WSAStartup(MAKEWORD(2, 2), &data);
|
||||
#else
|
||||
failed = wsa = 0;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr);
|
||||
@@ -22498,7 +22502,9 @@ mg_init_library(unsigned features)
|
||||
if (failed) {
|
||||
#if defined(_WIN32)
|
||||
if (wsa == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
}
|
||||
if (file_mutex_init == 0) {
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
@@ -22598,7 +22604,9 @@ mg_exit_library(void)
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
#else
|
||||
(void)pthread_mutexattr_destroy(&pthread_mutex_attr);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -227,7 +227,7 @@ int main(int argc, char *argv[]) {
|
||||
} else if (!strcasecmp(arg, "-i")) {
|
||||
identity = *++argv;
|
||||
} else {
|
||||
// nothing let's try to be smart and handle legacy crappy
|
||||
// nothing let's try to be smart and handle legacy crap
|
||||
if (!identity) identity = *argv;
|
||||
else if (!type) (void) !asprintf(&type, "%s.local", *argv);
|
||||
else if (!port) port = atoi(*argv);
|
||||
@@ -235,6 +235,7 @@ int main(int argc, char *argv[]) {
|
||||
txt = (const char**) malloc((argc + 1) * sizeof(char**));
|
||||
memcpy(txt, argv, argc * sizeof(char**));
|
||||
txt[argc] = NULL;
|
||||
break;
|
||||
}
|
||||
argc--;
|
||||
}
|
||||
@@ -250,13 +251,14 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
mdnsd_set_hostname(svr, hostname, host);
|
||||
svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt);
|
||||
mdns_service_destroy(svc);
|
||||
// mdns_service_destroy(svc);
|
||||
|
||||
#ifdef _WIN32
|
||||
Sleep(INFINITE);
|
||||
#else
|
||||
pause();
|
||||
#endif
|
||||
mdns_service_remove(svr, svc);
|
||||
mdnsd_stop(svr);
|
||||
} else {
|
||||
printf("Can't start server");
|
||||
@@ -264,7 +266,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
free(type);
|
||||
free(txt);
|
||||
if (txt) free(txt);
|
||||
|
||||
#ifdef _WIN32
|
||||
winsock_close();
|
||||
|
||||
@@ -144,11 +144,10 @@ static int create_recv_sock(uint32_t host) {
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
on = sizeof(on);
|
||||
socklen_t len;
|
||||
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, &len)) {
|
||||
socklen_t len = sizeof(on);
|
||||
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, &len)) {
|
||||
on = 1;
|
||||
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, sizeof(on))) < 0) {
|
||||
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %m\n", r);
|
||||
}
|
||||
}
|
||||
|
||||
0
components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py2
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/nanopb_generator.py2
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/protoc
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/protoc
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb
vendored
Normal file → Executable file
0
components/spotify/cspot/bell/external/nanopb/generator/protoc-gen-nanopb
vendored
Normal file → Executable 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/")
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class DecodersInstance {
|
||||
|
||||
void ensureAAC() {
|
||||
// if (aacDecoder == NULL) {
|
||||
// aacDecoder = AACInitDecoder();
|
||||
// aacDecoder = AACInitDecoder();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
using namespace bell;
|
||||
|
||||
std::mutex BellHTTPServer::initMutex;
|
||||
|
||||
class WebSocketHandler : public CivetWebSocketHandler {
|
||||
public:
|
||||
BellHTTPServer::WSDataHandler dataHandler;
|
||||
@@ -187,6 +189,7 @@ bool BellHTTPServer::handlePost(CivetServer* server,
|
||||
}
|
||||
|
||||
BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_init_library(0);
|
||||
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
|
||||
this->serverPort = serverPort;
|
||||
@@ -197,6 +200,11 @@ BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
server = std::make_unique<CivetServer>(civetWebOptions);
|
||||
}
|
||||
|
||||
BellHTTPServer::~BellHTTPServer() {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_exit_library();
|
||||
}
|
||||
|
||||
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
|
||||
const std::string& json, int status) {
|
||||
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace bell {
|
||||
class BellHTTPServer : public CivetHandler {
|
||||
public:
|
||||
BellHTTPServer(int serverPort);
|
||||
~BellHTTPServer();
|
||||
|
||||
enum class WSState { CONNECTED, READY, CLOSED };
|
||||
|
||||
@@ -100,6 +101,8 @@ class BellHTTPServer : public CivetHandler {
|
||||
std::mutex responseMutex;
|
||||
HTTPHandler notFoundHandler;
|
||||
|
||||
static std::mutex initMutex;
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
bool handlePost(CivetServer* server, struct mg_connection* conn);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#if __has_include("avahi-client/client.h")
|
||||
#include <avahi-client/client.h>
|
||||
@@ -40,8 +42,9 @@ class implMDNSService : public MDNSService {
|
||||
#endif
|
||||
static struct mdnsd* mdnsServer;
|
||||
static in_addr_t host;
|
||||
static std::atomic<size_t> instances;
|
||||
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
implMDNSService(struct mdns_service* service) : service(service){ instances++; };
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){};
|
||||
#endif
|
||||
@@ -50,6 +53,7 @@ class implMDNSService : public MDNSService {
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
in_addr_t implMDNSService::host = INADDR_ANY;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
static std::mutex registerMutex;
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
AvahiClient* implMDNSService::avahiClient = NULL;
|
||||
@@ -65,11 +69,21 @@ void implMDNSService::unregisterService() {
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
if (avahiGroup) {
|
||||
avahi_entry_group_free(avahiGroup);
|
||||
if (!--instances && implMDNSService::avahiClient) {
|
||||
avahi_client_free(implMDNSService::avahiClient);
|
||||
avahi_simple_poll_free(implMDNSService::avahiPoll);
|
||||
implMDNSService::avahiClient = nullptr;
|
||||
implMDNSService::avahiPoll = nullptr;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
}
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
@@ -179,19 +193,14 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
std::string type(serviceType + "." + serviceProto + ".local");
|
||||
|
||||
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
|
||||
struct mdns_service* mdnsService =
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
if (mdnsService) {
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
if (service) return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
|
||||
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s",
|
||||
serviceName.c_str());
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,12 @@ using namespace bell;
|
||||
class implMDNSService : public MDNSService {
|
||||
private:
|
||||
struct mdns_service* service;
|
||||
void unregisterService(void) {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
};
|
||||
void unregisterService(void);
|
||||
|
||||
public:
|
||||
static struct mdnsd* mdnsServer;
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
static std::atomic<size_t> instances;
|
||||
implMDNSService(struct mdns_service* service) : service(service) { instances++; };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,8 +32,18 @@ class implMDNSService : public MDNSService {
|
||||
**/
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
|
||||
static std::mutex registerMutex;
|
||||
|
||||
void implMDNSService::unregisterService() {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
const std::string& serviceName, const std::string& serviceType,
|
||||
const std::string& serviceProto, const std::string& serviceHost,
|
||||
@@ -94,5 +103,5 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
return service ? std::make_unique<implMDNSService>(service) : nullptr;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ void bell::enableSubmoduleLogging() {
|
||||
bell::bellGlobalLogger->enableSubmodule = true;
|
||||
}
|
||||
|
||||
void bell::enableTimestampLogging() {
|
||||
void bell::enableTimestampLogging(bool local) {
|
||||
bell::bellGlobalLogger->enableTimestamp = true;
|
||||
bell::bellGlobalLogger->shortTime = local;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class AbstractLogger {
|
||||
public:
|
||||
bool enableSubmodule = false;
|
||||
bool enableTimestamp = false;
|
||||
bool shortTime = false;
|
||||
|
||||
virtual void debug(std::string filename, int line, std::string submodule,
|
||||
const char* format, ...) = 0;
|
||||
@@ -94,10 +95,17 @@ class BellLogger : public bell::AbstractLogger {
|
||||
now.time_since_epoch()) %
|
||||
1000;
|
||||
|
||||
auto gmt_time = gmtime(&now_time);
|
||||
printf(colorReset);
|
||||
std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.'
|
||||
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
|
||||
struct tm* gmt_time;
|
||||
if (shortTime) {
|
||||
gmt_time = localtime(&now_time);
|
||||
std::cout << std::put_time(gmt_time, "[%H:%M:%S") << '.'
|
||||
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
|
||||
} else {
|
||||
gmt_time = gmtime(&now_time);
|
||||
std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.'
|
||||
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +137,7 @@ class BellLogger : public bell::AbstractLogger {
|
||||
|
||||
void setDefaultLogger();
|
||||
void enableSubmoduleLogging();
|
||||
void enableTimestampLogging();
|
||||
void enableTimestampLogging(bool local = false);
|
||||
} // namespace bell
|
||||
|
||||
#define BELL_LOG(type, ...) \
|
||||
|
||||
@@ -72,7 +72,11 @@ class Task {
|
||||
(LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL);
|
||||
return thread != NULL;
|
||||
#else
|
||||
return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0);
|
||||
if (!pthread_create(&thread, NULL, taskEntryFunc, this)) {
|
||||
pthread_detach(thread);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,13 +112,7 @@ class Task {
|
||||
#endif
|
||||
|
||||
static void* taskEntryFunc(void* This) {
|
||||
Task* self = (Task*)This;
|
||||
self->runTask();
|
||||
#if _WIN32
|
||||
WaitForSingleObject(self->thread, INFINITE);
|
||||
#else
|
||||
pthread_join(self->thread, NULL);
|
||||
#endif
|
||||
((Task*)This)->runTask();
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,11 +48,12 @@ class SpircHandler {
|
||||
|
||||
void setPause(bool pause);
|
||||
|
||||
void previousSong();
|
||||
bool previousSong();
|
||||
|
||||
void nextSong();
|
||||
bool nextSong();
|
||||
|
||||
void notifyAudioReachedPlayback();
|
||||
void notifyAudioEnded();
|
||||
void updatePositionMs(uint32_t position);
|
||||
void setRemoteVolume(int volume);
|
||||
void loadTrackFromURI(const std::string& uri);
|
||||
@@ -74,7 +75,7 @@ class SpircHandler {
|
||||
void sendEvent(EventType type);
|
||||
void sendEvent(EventType type, EventData data);
|
||||
|
||||
void skipSong(TrackQueue::SkipDirection dir);
|
||||
bool skipSong(TrackQueue::SkipDirection dir);
|
||||
void handleFrame(std::vector<uint8_t>& data);
|
||||
void notify();
|
||||
};
|
||||
|
||||
@@ -32,10 +32,11 @@ struct TrackReference;
|
||||
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<void(std::shared_ptr<QueuedTrack>, bool)>
|
||||
TrackLoadedCallback;
|
||||
typedef std::function<size_t(uint8_t*, size_t, std::string_view)>
|
||||
DataCallback;
|
||||
typedef std::function<void()> EOFCallback;
|
||||
typedef std::function<bool()> isAiringCallback;
|
||||
|
||||
TrackPlayer(std::shared_ptr<cspot::Context> ctx,
|
||||
std::shared_ptr<cspot::TrackQueue> trackQueue,
|
||||
@@ -48,7 +49,7 @@ class TrackPlayer : bell::Task {
|
||||
|
||||
// CDNTrackStream::TrackInfo getCurrentTrackInfo();
|
||||
void seekMs(size_t ms);
|
||||
void resetState();
|
||||
void resetState(bool paused = false);
|
||||
|
||||
// Vorbis codec callbacks
|
||||
size_t _vorbisRead(void* ptr, size_t size, size_t nmemb);
|
||||
@@ -88,6 +89,7 @@ class TrackPlayer : bell::Task {
|
||||
std::atomic<bool> pendingReset = false;
|
||||
std::atomic<bool> inFuture = false;
|
||||
std::atomic<size_t> pendingSeekPositionMs = 0;
|
||||
std::atomic<bool> startPaused = false;
|
||||
|
||||
std::mutex runningMutex;
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -24,7 +24,7 @@ class CDNAudioFile;
|
||||
// Used in got track info event
|
||||
struct TrackInfo {
|
||||
std::string name, album, artist, imageUrl, trackId;
|
||||
uint32_t duration;
|
||||
uint32_t duration, number, discNumber;
|
||||
|
||||
void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid);
|
||||
void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid);
|
||||
@@ -54,6 +54,7 @@ class QueuedTrack {
|
||||
|
||||
uint32_t requestedPosition;
|
||||
std::string identifier;
|
||||
bool loading = false;
|
||||
|
||||
// Will return nullptr if the track is not ready
|
||||
std::shared_ptr<cspot::CDNAudioFile> getAudioFile();
|
||||
@@ -94,14 +95,13 @@ class TrackQueue : public bell::Task {
|
||||
std::shared_ptr<bell::WrappedSemaphore> playableSemaphore;
|
||||
std::atomic<bool> notifyPending = false;
|
||||
|
||||
|
||||
void runTask() override;
|
||||
void stopTask();
|
||||
|
||||
bool hasTracks();
|
||||
bool isFinished();
|
||||
bool skipTrack(SkipDirection dir, bool expectNotify = true);
|
||||
void updateTracks(uint32_t requestedPosition = 0, bool initial = false);
|
||||
bool updateTracks(uint32_t requestedPosition = 0, bool initial = false);
|
||||
TrackInfo getTrackInfo(std::string_view identifier);
|
||||
std::shared_ptr<QueuedTrack> consumeTrack(
|
||||
std::shared_ptr<QueuedTrack> prevSong, int& offset);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -32,6 +32,8 @@ message Artist {
|
||||
message Track {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 number = 5;
|
||||
optional sint32 disc_number = 6;
|
||||
optional sint32 duration = 0x7;
|
||||
optional Album album = 0x3;
|
||||
repeated Artist artist = 0x4;
|
||||
@@ -44,6 +46,7 @@ message Episode {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 duration = 7;
|
||||
optional sint32 number = 65;
|
||||
repeated AudioFile file = 12;
|
||||
repeated Restriction restriction = 0x4B;
|
||||
optional ImageGroup covers = 0x44;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user