Compare commits

...

105 Commits

Author SHA1 Message Date
Philippe G
df777e83d1 Some SPI displays require cs_post adjusted per speed - release 2022-01-05 11:53:29 -08:00
Philippe G
d0461d55e4 minor tweaks 2022-01-04 21:22:38 -08:00
Philippe G
1c071d03dd release 2021-12-31 21:46:05 -08:00
Philippe G
0d142e7515 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-31 21:44:41 -08:00
philippe44
417b683401 Update README.md 2021-12-31 19:14:32 -08:00
Philippe G
e8f8239ee3 GPIO expander intr fix + some display need post CS time when switching rate between SPI devices - release 2021-12-31 18:25:59 -08:00
Philippe G
9789bef653 BT init can be called with stack in PSRAM + update source driver - release 2021-12-30 18:18:41 -08:00
Philippe G
d2f8d51c28 simplify dependencies 2021-12-28 22:44:05 -08:00
Philippe G
1ee0a232f4 can't shift by more than length - release 2021-12-27 22:05:23 -08:00
Philippe G
0d411d7295 slimproto alignment with 4.3 (decode_restore) - release 2021-12-24 13:07:38 -08:00
Philippe G
fff5a6bf03 unify (as much as possible) 4.0 and 4.3 for AirPlay 2021-12-22 18:37:44 -08:00
Philippe G
bf9410904d thread names 2021-12-22 16:29:41 -08:00
Philippe G
fed174a062 SB displayer waits when external decoder is in control 2021-12-21 23:21:59 -08:00
Philippe G
efc2c9515c BT/RAOP switch improvments + pin tasks to core 1 (pthread) 2021-12-21 23:19:02 -08:00
philippe44
29b3eee5b6 Update README.md 2021-12-21 02:00:56 -08:00
philippe44
20a04550a8 Update README.md 2021-12-21 01:58:05 -08:00
Philippe G
c95302def1 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-21 01:04:37 -08:00
Philippe G
e9890227a4 only update VU/spectrum if we own the display 2021-12-21 01:04:34 -08:00
philippe44
b3eec4b7d9 Update README.md 2021-12-21 00:32:15 -08:00
philippe44
6c0e4fc589 Update README.md 2021-12-21 00:30:41 -08:00
Philippe G
16ec4c0d57 correction - release 2021-12-20 23:40:57 -08:00
Philippe G
b13fa88033 optimize telnet, stacks and threads 2021-12-20 23:32:02 -08:00
Philippe G
de86a10d82 a bit of telnet refactoring + fix socket leak - release 2021-12-19 22:07:01 -08:00
Philippe G
7f57cd5338 minor non-functional fixes 2021-12-18 23:30:55 -08:00
Philippe G
975f34f01b Give some love to AIrPlay/BT screens + better "safe TCB release" - release 2021-12-18 15:20:22 -08:00
Philippe G
f872623785 release 2021-12-17 21:34:55 -08:00
Philippe G
326ddb6973 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-17 21:32:36 -08:00
Philippe G
1c92fdcc96 fix pca8575 - release 2021-12-17 21:32:03 -08:00
philippe44
828a36a9d1 Update README.md 2021-12-17 21:30:58 -08:00
Philippe G
f330314795 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-17 10:54:29 -08:00
Philippe G
3b6299dc1a AirPlay: no realloc, safe TCB cleanup, tools convergence 2021-12-17 10:54:25 -08:00
Sébastien
2055025468 Add idf4.3 build to the default branch 2021-12-17 10:52:15 -05:00
Sébastien
458d7eacbe Update CrossBuild.yml 2021-12-17 09:52:36 -05:00
Philippe G
a266c07114 move to stock esp_http_server but keep it under control
- means 3 sockets are used (data + 2 for control)
- but LRU is activated (uses the 2 extra sockets to wake from select)
- backlog is just 1 (listen)
- only 3 sockets can be consumed before LRU activates
- for now, connections are kept-alive
2021-12-14 11:52:51 -08:00
Philippe G
412880d628 bring back ctrl message in http_server - part#1 2021-12-13 19:53:02 -08:00
Philippe G
518c2d24e9 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-12 22:16:50 -08:00
Philippe G
7b361a0c2c release 2021-12-12 22:16:46 -08:00
philippe44
333b295471 Update README.md 2021-12-10 23:27:28 -08:00
philippe44
a60cbe53e2 Update README.md 2021-12-10 23:26:51 -08:00
philippe44
5d255ea502 Update README.md 2021-12-10 23:25:05 -08:00
Philippe G
344730e1bc optimize GPIO expander + external decoder fix
- external decoders sink callback had infinite loop when output buffer was full and would never empty
- race condition when playback stopped/restarted while waiting for output buffer to empty
2021-12-10 23:03:13 -08:00
Sébastien
af7c181063 Delete esp-idf-v4.3-build 2021-12-10 16:27:10 -05:00
Sébastien
cf3393e76a Update cache path to split esp-idf v4.3 2021-12-10 16:11:55 -05:00
Sébastien
4474c4378a Remove ethernet branch from build 2021-12-10 15:51:29 -05:00
Sébastien
f8079ea9e2 Create esp-idf-v4.3-build 2021-12-10 15:50:51 -05:00
Philippe G
af48710e31 Validate MCP23S17 + typos correction 2021-12-10 12:10:26 -08:00
philippe44
a59e1d9e54 Update README.md 2021-12-10 12:04:48 -08:00
Philippe G
65a1b4d7f3 Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-12-06 18:29:15 -08:00
Philippe G
a9efcfeca8 safe NVS parsing 2021-12-06 18:29:04 -08:00
philippe44
f38c2b8cb4 Update README.md 2021-12-05 18:25:12 -08:00
Philippe G
5ac153f808 Add MCP23s17 + further optimizations 2021-12-05 18:21:39 -08:00
Philippe G
ec6dcb83f8 missing %d in expander isr log 2021-12-04 21:09:55 -08:00
Philippe G
16ba8e9e1f small correction for MCP23017 that now works! 2021-12-04 19:32:52 -08:00
philippe44
baaabb7d99 Update README.md 2021-12-04 19:28:42 -08:00
Philippe G
d2494b73db Rotary encoder + simplify
- GPIO expander works with rotary encoder
- Much better mimic real GPIO, including ISR, to minimize impact on clients
2021-12-04 12:40:56 -08:00
philippe44
f8bfb807d9 Update README.md 2021-12-04 12:39:01 -08:00
philippe44
30a891ffc8 Update README.md 2021-12-04 12:38:29 -08:00
Philippe G
1649a7e2a0 add trace for ST7735 offset 2021-12-02 11:10:30 -08:00
Philippe G
2fbea79a5b Add offset for ST7735 2021-12-02 11:04:52 -08:00
philippe44
9f9f171c9a Update README.md 2021-12-02 11:04:37 -08:00
philippe44
eebbf76466 Update README.md 2021-12-02 11:03:38 -08:00
Philippe G
2a9d87b6c5 add multi-expander 2021-12-01 19:15:29 -08:00
philippe44
f655244039 Update README.md 2021-12-01 19:15:08 -08:00
Philippe G
034f64c62a add mcp23017 (untested) and refactor things a bit 2021-12-01 18:20:07 -08:00
philippe44
9848f2d7d7 Update README.md 2021-12-01 18:19:41 -08:00
Philippe G
c0f5ca1e10 fix expander as output (works now on 9535) 2021-12-01 14:28:32 -08:00
Philippe G
559f4ce69f SPI can only be HSPI + remove unused defines in globdefs 2021-12-01 09:30:34 -08:00
philippe44
cde936eb60 Update README.md 2021-12-01 09:23:38 -08:00
philippe44
652b921059 Update README.md 2021-12-01 00:54:44 -08:00
Philippe G
6fb458eff7 min expanded GPIO is based on esp32's data 2021-11-30 23:56:54 -08:00
philippe44
3b9ef05c6e Update README.md 2021-11-30 23:56:00 -08:00
Philippe G
970c72d416 add writable expander GPIOs 2021-11-30 23:51:52 -08:00
philippe44
f7a7ece582 Update README.md 2021-11-30 22:14:12 -08:00
Philippe G
5c99ab56f9 create queue before task... 2021-11-30 21:41:48 -08:00
Philippe G
62b0b1fac0 first function pcx9535 version 2021-11-30 21:28:52 -08:00
Philippe G
4ee36c24f4 add async write and mutex 2021-11-30 17:10:59 -08:00
Philippe G
3d123e86ac add pc(a/f)85xx (untested) and write mode
Still no mutex
2021-11-29 23:29:17 -08:00
Philippe G
507c2c9755 first commit of GPIO expander 2021-11-29 19:24:52 -08:00
Philippe G
016bc1bb4d adjusting telnet stack size 2021-11-27 00:46:21 -08:00
Philippe G
17a3058b23 cleaning 2021-11-27 00:11:10 -08:00
Philippe G
92260f939d trace backport error 2021-11-26 18:15:23 -08:00
Philippe G
bbca38aaec Can't set NDEBUG with bluedroid (compiler optimization bug) + remove code when BT/AirPlay are not used 2021-11-26 18:07:20 -08:00
Philippe G
ec860480a9 no call to BT in decoder init when BT is compiled out 2021-11-26 00:48:40 -08:00
Philippe G
d4fa8638d7 Timer stack needs a bit more room + no EXT_RAM_ATTR for .data (again) 2021-11-26 00:32:36 -08:00
Philippe G
c2f3e225d2 memory leak in telnet + more tweaks 2021-11-25 19:29:31 -08:00
Philippe G
bc0783f7b9 don't use pthread_attr if we want esp.cfg to prevail 2021-11-25 18:31:19 -08:00
Philippe G
8de6a5de68 remove BT stack logs and gain 10kB of IRAM 2021-11-25 17:41:33 -08:00
Philippe G
a07cdbf3b5 optimize queue sizes and move more data to SPIRAM 2021-11-25 01:22:04 -08:00
Philippe G
a8521223c9 forgot to push telnet.c 2021-11-24 23:24:10 -08:00
Philippe G
ada74ab127 telnet & http server send command to stdin of console
The NVS commands must be executed using an internal stack, so push all keyboard-like inputs to the console
2021-11-24 23:15:13 -08:00
philippe44
4943df2ec7 Update README.md 2021-11-20 11:09:44 -08:00
Philippe G
9588ae9f39 amp gpio control with jack - release 2021-11-20 11:08:48 -08:00
philippe44
c0c37bd98a Update CrossBuild.yml 2021-11-18 23:07:58 -08:00
philippe44
d5a8e3a4a2 Update CrossBuild.yml 2021-11-18 23:06:16 -08:00
Philippe G
d4b0bc4edb SPIRAM cannot be used for initialized data - release 2021-11-18 18:37:18 -08:00
Philippe G
635d382d71 fully remove ctrl_fd (was uninitialized in server loop) - release 2021-11-18 12:22:48 -08:00
philippe44
5d09d4f853 Update README.md 2021-11-17 10:59:44 -08:00
Philippe G
33c4c1fb31 release 2021-11-17 10:34:06 -08:00
Philippe G
e0d76281df Merge branch 'master-cmake' of https://github.com/sle118/squeezelite-esp32 into master-cmake 2021-11-17 10:31:22 -08:00
Philippe G
b3e67a8571 backport typo - release 2021-11-17 10:31:19 -08:00
philippe44
1e36180f05 Update README.md 2021-11-17 10:28:57 -08:00
Philippe G
08b22504bc allow array to be used in dac_controlset - release 2021-11-17 10:24:01 -08:00
philippe44
c236044228 limit sockets used by HTTP server (#122)
seems that ctrl and msg sockets are not needed
2021-11-17 07:59:30 -05:00
Philippe G
faa9976d3d switching or losing server connection was exhausting sockets - release 2021-11-13 19:17:50 -08:00
Philippe G
1dbffe6753 no EXTRAM attribute + cosmetics - release 2021-11-13 16:43:51 -08:00
77 changed files with 2352 additions and 1729 deletions

View File

@@ -1,11 +1,12 @@
# This is a basic workflow to help you get started with Actions
name: Cross-Build
on:
push:
branches-ignore: [ master ]
branches:
- 'master-cmake'
pull_request:
branches-ignore: [ master ]
branches:
- 'master-cmake'
jobs:
job1:

157
.github/workflows/esp-idf-v4.3-build.yml vendored Normal file
View File

@@ -0,0 +1,157 @@
# This is a basic workflow to help you get started with Actions
name: ESP-IDF v4.3.1
on:
push:
branches:
- '**4.3'
pull_request:
branches:
- '**4.3'
jobs:
job1:
name: Build Number
runs-on: ubuntu-latest
outputs:
build_number: ${{ steps.buildnumber.outputs.build_number }}
steps:
- name: Generate common build number
id: buildnumber
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
build:
runs-on: ubuntu-latest
needs: job1
strategy:
max-parallel: 1
matrix:
node: [I2S-4MFlash, SqueezeAmp]
depth: [16, 32]
steps:
- name: Set target name
run: |
echo "TARGET_BUILD_NAME=${{ matrix.node }}" >> $GITHUB_ENV
echo "build_version_prefix=1." >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
fetch-depth: 15
submodules: true
- name: Cache build
id: cache-build
uses: actions/cache@v2
with:
path: |
build
/var/lib/docker
key: idf4.3-${{ runner.os }}-${{ matrix.node }}-${{ matrix.depth }}
- name: Set build parameters
run: |
git update-index --chmod=+x ./server_certs/getcert.sh
cd server_certs;./getcert.sh;cat github.pem;cd ..
shopt -s nocasematch
branch_name="${GITHUB_REF//refs\/heads\//}"
branch_name="${branch_name//[^a-zA-Z0-9\-~!@_\.]/}"
BUILD_NUMBER=${{ needs.job1.outputs.build_number }}
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV
tag="${TARGET_BUILD_NAME}.${{matrix.depth}}.${BUILD_NUMBER}.${branch_name}"
echo "tag=${tag}" >> $GITHUB_ENV
last_commit="$(git log --pretty=format:'%s' --max-count=1)"
if [[ "$last_commit" =~ .*"Release".* ]]; then echo "release_flag=1" >> $GITHUB_ENV; else echo "release_flag=0" >> $GITHUB_ENV; fi
name="1.${BUILD_NUMBER}-${{matrix.depth}}#v4.0#${TARGET_BUILD_NAME}#${branch_name}"
artifact_prefix="squeezelite-esp32-${branch_name}-${TARGET_BUILD_NAME}-${{matrix.depth}}-${build_version_prefix}${BUILD_NUMBER}"
artifact_file_name="${artifact_prefix}.zip"
artifact_bin_file_name="${artifact_prefix}.bin"
echo "name=${name}" >> $GITHUB_ENV
echo "last_commit=${last_commit}" >> $GITHUB_ENV
echo "artifact_file_name=${artifact_file_name}" >> $GITHUB_ENV
echo "PROJECT_VER=${TARGET_BUILD_NAME}-${{ steps.buildnumber.outputs.build_number }} " >> $GITHUB_ENV
echo "artifact_bin_file_name=${artifact_bin_file_name}" >> $GITHUB_ENV
description=""
description=${description}$'------------------------------\n### Revision Log\n\n'
description="$description$(git log --pretty=format:'%h %s (%cI) <%an>' --abbrev-commit --max-count=15 | sed --r 's/(^[\*]+)/\\\1/g') "
echo 'description<<~EOD' >> $GITHUB_ENV
echo ${description}>> $GITHUB_ENV
echo '~EOD' >> $GITHUB_ENV
echo #######
echo ####### Environment
echo #######
env
echo #######
echo ####### GITHUB ENV
echo #######
cat $GITHUB_ENV
- name: Build the firmware
run: |
env | grep "artifact\|tag\|GITHUB\|version\|NUMBER\|TARGET" >${TARGET_BUILD_NAME}-env.txt
echo "${tag}" >version.txt
echo pulling docker version 4.3.1
docker pull espressif/idf:v4.3.1
docker info
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project espressif/idf:v4.3.1 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build -DDEPTH=${{ matrix.depth }} -DBUILD_NUMBER=${BUILD_NUMBER}-${{ matrix.depth }} && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
# - name: Build Mock firmware
# run: |
# mkdir -p build
# cd build
# mkdir -p partition_table
# mkdir -p bootloader
# echo "mock content"> squeezelite.bin
# echo "mock content"> recovery.bin
# echo "mock content"> ./bootloader/bootloader.bin
# echo "mock content"> ./partition_table/partition-table.bin
# echo "mock content"> flash_project_args
# echo "mock content"> size_comp1.txt
# echo "mock content"> size_comp2.txt
# echo "mock content"> ../partitions.csv
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_file_name }}
path: |
build/*.bin
build/bootloader/bootloader.bin
build/partition_table/partition-table.bin
build/flash_project_args
build/size_comp1.txt
build/size_comp2.txt
partitions.csv
sdkconfig
server_certs/github.pem
build_output.zip
- uses: actions/upload-artifact@v2
with:
name: ${{ env.artifact_bin_file_name }}
path: |
build/squeezelite.bin
- name: Create Release
if: env.release_flag == 1
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
with:
tag_name: ${{ env.tag }}
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
- name: Upload Release Asset - Squeezelite binary file
if: env.release_flag == 1
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/squeezelite.bin
asset_name: ${{ env.artifact_bin_file_name }}
asset_content_type: application/octet-stream
- name: Upload Release Asset - Zip file
if: env.release_flag == 1
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: build/${{ env.artifact_file_name }}
asset_name: ${{ env.artifact_file_name }}
asset_content_type: application/octet-stream

View File

@@ -71,7 +71,7 @@ NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hard
- spdif_config: bck=33,ws=25,do=15
### ESP32-A1S
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 (see below)
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection. Note that there is a version with AC101 codec and another one with ES8388 with probably two variants - these boards are a mess (see below)
The board shown above has the following IO set
- amplifier: GPIO21
@@ -95,8 +95,10 @@ So a possible config would be
for AC101
- dac_config: model=AC101,bck=27,ws=26,do=25,di=35,sda=33,scl=32
for ES8388
for ES8388 (it seems that there are variants with same version number - a total mess)
- dac_config: model=ES8388,bck=5,ws=25,do=26,sda=18,scl=23,i2c=16
or
- dac_config: model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,i2c=16
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
@@ -136,11 +138,11 @@ sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
```
<strong>Please note that you can not use the same GPIO or port as the DAC</strong>
### SPI
The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The NVS parameter "spi_config" set the spi's gpio used for generic purpose (e.g. display). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
The esp32 has 3 user-accessible SPI sub-systems but SPI0 and SPI2 are reserved for internal use and Flash/PSRAM, so only SPI1 is available. The NVS parameter "spi_config" set the spi's gpio used for user purpose (e.g. display, ethernet, GPIO expander). Leave it blank to disable SPI usage. The DC parameter is needed for displays. Syntax is
```
data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1|2][,miso=<gpio>]
data|mosi=<gpio>,clk=<gpio>[,dc=<gpio>][,host=1][,miso=<gpio>]
```
Default "host" is 1. The "miso" parameter is only used when SPI bus is to be shared with other peripheral (e.g. ethernet, see below), otherwise it can be omitted. Note that "data" can also be named "mosi".
Default and only "host" is 1 as others are used already by flash and spiram. The optional "miso" (MasterInSlaveOut) parameter is only used when SPI bus is bi-directional and shared with other peripheral like ethernet, gpio expander. Note that "data" can also be named "mosi" (MasterOutSlaveIn).
### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for SqueezeAMP and A1S where these are forced at runtime. Syntax is
```
@@ -148,13 +150,15 @@ bck=<gpio>,ws=<gpio>,do=<gpio>[,mck][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|A
```
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). Only GPIO0 can be used as MCLK and 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 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, speakder and headset on and off using a JSON syntax:
```
{ init: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweron: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
poweroff: [ {"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>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
... }
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
Where \<command\> is one of init, poweron, poweroff, speakeron, speakeroff, headseton, headsetoff
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
NB: For specific builds (all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
@@ -184,24 +188,24 @@ GPIO ----210ohm-----------||---- coax S/PDIF signal out
Ground -------------------------- coax signal ground
```
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
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|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[:x=<offset>][:y=<offset>]|ST7789|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
- 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.
- VFlip and HFlip are optional can be used to change display orientation
- rotate: for non-square *drivers*, move to portrait mode. Note that *width* and *height* must be inverted then
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
- SH1106 is 128x64 monochrome I2C/SPI [here](https://www.waveshare.com/wiki/1.3inch_OLED_HAT)
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control. Some have X/Y offsets betwen the driver and the glass (green/black/red models) that can be added using "x" and "y" options (case sensitive!)
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
- ILI9341 is another 240x320 65k (262k capable) color SPI. I've not used it much, the driver it has been provided by one external contributor to the project
@@ -212,9 +216,7 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
[format=<display_content>][,speed=<speed>][,pause=<pause>]
```
- 'speed' is the scrolling speed in ms (default is 33ms)
- 'pause' is the pause time between scrolls in ms (default is 3600ms)
- 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
### Infrared
@@ -246,6 +248,26 @@ Syntax is:
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
### GPIO expanders
It is possible to add GPIO expanders using I2C or SPI bus. They should mainly be used for buttons but they can support generic-purpose outputs as well. These additional GPIOs can be numbered starting from an arbitrary value (40 and above as esp32 has GPIO 0..39). Then these new "virtual" GPIOs from (e.g) 100 to 115 can be used in [button](#Buttons) configuration, [set_GPIO](#set-gpio) or other config settings.
Each expander can support up to 32 GPIO. To use an expander for buttons, an interrupt must be provided, polling mode is not acceptable. An expander w/o interruption can still be configured, but only output will be usable. Note that the same interrupt can be shared accross expanders, as long as they are using open drain or open collectors (which they probably all do)
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>|100][,count=<n>|16][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
- speed (SPI): speed of the SPI bus for that device (in Hz)
- base: GPIO numbering offset to use everywhere else (default 40)
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
- intr: real GPIO to use as interrupt.
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
@@ -253,6 +275,7 @@ See §**set_GPIO** for how to set the green and red LEDs. In addition, their bri
```
NB: For well-known configuration, this is ignored
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
Encoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay. Using the option 'volume' makes it hard-coded to volume down/up/play toggle all the time (even in LMS). The option 'longpress' allows an alternate mode when SW is long-pressed. In that mode, left is previous, right is next and press is toggle. Every long press on SW alternates between modes (the main mode actual behavior depends on 'volume').
@@ -372,8 +395,8 @@ The benefit of the "raw" mode is that you can build a player which is as close a
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
### Ethernet (coming soon)
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or Davicom DM9051 over SPI like [that](https://www.amazon.com/dp/B08JLFWX9Z).
### Ethernet (required unpublished version 4.3)
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
#### RMII (LAN8720)
- RMII PHY wiring is fixed and can not be changed
@@ -395,23 +418,21 @@ Connecting a reset pin for the LAN8720 is optional but recommended to avoid that
- Clock
The APLL of the esp32 is required for the audio codec, so we **need** a LAN8720 that provides a 50MHz clock. That clock **must** be connected to GPIO0, there is no alternative. This means that if your DAC requires an MCLK, then you are out of luck. It is not possible to have both to work together. There might be some workaround using CLK_OUT2 and GPIO3, but I don't have time for this.
#### SPI (DM9051)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. Another benefit is that the SPI bus can be shared with the display, but it's also possible to have a dedicated SPI interface. The esp32 has 4 SPI sub-systems, one is unaccessible so numbering is 0..2 and SPI0 is reserved for Flash/PSRAM. The "eth_config" parameter syntax becomes:
#### SPI (DM9051 or W5500)
Ethernet over SPI is supported as well and requires less GPIOs but is obvsiously slower. SPI is the shared bus set with [spi_config](#spi). The "eth_config" parameter syntax becomes:
```
model=dm9051,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,host=<-1|1|2>][,rst=<gpio>][,mosi=<gpio>,miso=<gpio>,clk=<gpio>]
model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
```
- To use the system SPI, shared with display (see spi_config) "host" must be set to -1. Any other value will reserve the SPI interface (careful of conflict with spi_config). The default "host" is 2 to avoid conflicting wiht default "spi_config" settings.
- When not using system SPI, "mosi" for data out, "miso" for data in and "clk" **must** be set
- The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details)
- The reset pin is optional but recommended
- The esp32 has a special I/O multiplexer for faster speed (up to 80 MHz) but that requires using specific GPIOs, which depends on SPI bus (See [here](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) for more details). Note that currently only SPI2 is available.
| Pin Name | SPI2 | SPI3 |
| Pin Name | SPI1 | SPI2 |
| -------- | ---- | ---- |
| CS0* | 15 | 5 |
| CS | 15 | 5 |
| SCLK | 14 | 18 |
| MISO | 12 | 19 |
| MOSI | 13 | 23 |
** THIS IS NOT AVAILABLE YET, SO MORE TO COME ON HOW TO USE WIRED ETHERNET***
### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
```

View File

@@ -11,6 +11,7 @@ CONFIG_DISPLAY_CONFIG=""
CONFIG_I2C_CONFIG=""
CONFIG_SPI_CONFIG=""
CONFIG_SET_GPIO=""
CONFIG_GPIO_EXP_CONFIG=""
CONFIG_ROTARY_ENCODER=""
CONFIG_LED_GREEN_GPIO=-1
CONFIG_LED_GREEN_GPIO_LEVEL=1
@@ -130,8 +131,8 @@ CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE is not set
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
@@ -185,175 +186,48 @@ CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_SSP_ENABLED=y
CONFIG_BT_BLE_ENABLED=n
CONFIG_BT_BLE_SMP_ENABLE=y
CONFIG_BT_STACK_NO_LOG=n
CONFIG_BT_STACK_NO_LOG=y
CONFIG_BT_LOG_HCI_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_HCI_TRACE_LEVEL=2
CONFIG_BT_LOG_BTM_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_BTM_TRACE_LEVEL=2
CONFIG_BT_LOG_L2CAP_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_L2CAP_TRACE_LEVEL=2
CONFIG_BT_LOG_RFCOMM_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_RFCOMM_TRACE_LEVEL=2
CONFIG_BT_LOG_SDP_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_SDP_TRACE_LEVEL=2
CONFIG_BT_LOG_GAP_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_GAP_TRACE_LEVEL=2
CONFIG_BT_LOG_BNEP_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_BNEP_TRACE_LEVEL=2
CONFIG_BT_LOG_PAN_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_PAN_TRACE_LEVEL=2
CONFIG_BT_LOG_A2D_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_A2D_TRACE_LEVEL=2
CONFIG_BT_LOG_AVDT_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_AVDT_TRACE_LEVEL=2
CONFIG_BT_LOG_AVCT_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_AVCT_TRACE_LEVEL=2
CONFIG_BT_LOG_AVRC_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_AVRC_TRACE_LEVEL=2
CONFIG_BT_LOG_MCA_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_MCA_TRACE_LEVEL=2
CONFIG_BT_LOG_HID_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_HID_TRACE_LEVEL=2
CONFIG_BT_LOG_APPL_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_APPL_TRACE_LEVEL=2
CONFIG_BT_LOG_GATT_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_GATT_TRACE_LEVEL=2
CONFIG_BT_LOG_SMP_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_SMP_TRACE_LEVEL=2
CONFIG_BT_LOG_BTIF_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_BTIF_TRACE_LEVEL=2
CONFIG_BT_LOG_BTC_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_BTC_TRACE_LEVEL=2
CONFIG_BT_LOG_OSI_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_OSI_TRACE_LEVEL=2
CONFIG_BT_LOG_BLUFI_TRACE_LEVEL_WARNING=y
CONFIG_BT_LOG_BLUFI_TRACE_LEVEL=2
CONFIG_BT_ACL_CONNECTIONS=4
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y
@@ -593,7 +467,7 @@ CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set
CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2432
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2800
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
#CONFIG_FREERTOS_USE_TRACE_FACILITY=y
@@ -959,7 +833,7 @@ CONFIG_MB_TIMER_INDEX=0
CONFIG_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2432
CONFIG_TIMER_TASK_STACK_DEPTH=2800
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_L2_TO_L3_COPY is not set
# CONFIG_USE_ONLY_LWIP_SELECT is not set

View File

@@ -12,6 +12,7 @@ CONFIG_DISPLAY_CONFIG=""
CONFIG_I2C_CONFIG=""
CONFIG_SPI_CONFIG=""
CONFIG_SET_GPIO=""
CONFIG_GPIO_EXP_CONFIG=""
CONFIG_ROTARY_ENCODER=""
CONFIG_LED_GREEN_GPIO=-1
CONFIG_LED_GREEN_GPIO_LEVEL=1
@@ -129,8 +130,8 @@ CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE is not set
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
@@ -185,7 +186,7 @@ CONFIG_BT_SSP_ENABLED=y
# CONFIG_BT_BLE_ENABLED is not set
CONFIG_BT_BLE_ENABLED=n
CONFIG_BT_BLE_SMP_ENABLE=y
CONFIG_BT_STACK_NO_LOG=n
CONFIG_BT_STACK_NO_LOG=y
CONFIG_BT_ACL_CONNECTIONS=4
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y
@@ -422,7 +423,7 @@ CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set
CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2432
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2800
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
#CONFIG_FREERTOS_USE_TRACE_FACILITY=y
@@ -793,7 +794,7 @@ CONFIG_MB_TIMER_INDEX=0
CONFIG_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2432
CONFIG_TIMER_TASK_STACK_DEPTH=2800
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_L2_TO_L3_COPY is not set
# CONFIG_USE_ONLY_LWIP_SELECT is not set

View File

@@ -16,6 +16,7 @@ CONFIG_DISPLAY_CONFIG=""
CONFIG_I2C_CONFIG=""
CONFIG_SPI_CONFIG=""
CONFIG_SET_GPIO=""
CONFIG_GPIO_EXP_CONFIG=""
CONFIG_ROTARY_ENCODER=""
CONFIG_LED_GREEN_GPIO=12
CONFIG_LED_GREEN_GPIO_LEVEL=0
@@ -129,8 +130,8 @@ CONFIG_DEFAULT_AP_BEACON_INTERVAL=100
CONFIG_DEFAULT_COMMAND_LINE="squeezelite -o I2S -b 500:2000 -d all=info -C 30 -W"
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE is not set
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
@@ -184,7 +185,7 @@ CONFIG_BT_A2DP_ENABLE=y
# CONFIG_BT_HFP_ENABLE is not set
CONFIG_BT_SSP_ENABLED=y
# CONFIG_BT_BLE_ENABLED is not set
CONFIG_BT_STACK_NO_LOG=n
CONFIG_BT_STACK_NO_LOG=y
CONFIG_BT_ACL_CONNECTIONS=4
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y
@@ -424,7 +425,7 @@ CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set
CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2432
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2800
CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
#CONFIG_FREERTOS_USE_TRACE_FACILITY=y
@@ -789,7 +790,7 @@ CONFIG_MB_TIMER_INDEX=0
CONFIG_SUPPORT_STATIC_ALLOCATION=y
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2432
CONFIG_TIMER_TASK_STACK_DEPTH=2800
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_L2_TO_L3_COPY is not set
# CONFIG_USE_ONLY_LWIP_SELECT is not set

View File

@@ -1,5 +1,5 @@
idf_component_register(
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
)
if (DEFINED AAC_DISABLE_SBR)

View File

@@ -86,6 +86,10 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
Device->WriteCommand( Device, Contrast );
}
static void SPIParams(int Speed, uint8_t *mode, uint8_t *CS_pre, uint8_t *CS_post) {
*CS_post = Speed / (8*1000*1000);
}
static bool Init( struct GDS_Device* Device ) {
#ifdef SHADOW_BUFFER
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
@@ -140,6 +144,7 @@ static const struct GDS_Device SH1106 = {
.SetLayout = SetLayout,
.Update = Update, .Init = Init,
.Depth = 1,
.SPIParams = SPIParams,
#if !defined SHADOW_BUFFER && defined USE_IRAM
.Alloc = GDS_ALLOC_IRAM_SPI;
#endif

View File

@@ -243,7 +243,7 @@ struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
char *p;
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->ReadyPin = -1;
if ((p = strcasestr(Driver, "ready")) != NULL) Private->ReadyPin = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(Driver, "ready")) && (p = strchr(p, '='))) Private->ReadyPin = atoi(p + 1);
ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin);

View File

@@ -199,8 +199,8 @@ static void SetLayout( struct GDS_Device* Device, bool HFlip, bool VFlip, bool R
WriteByte( Device, Private->MADCtl );
if (Private->Model == ST7789) {
if (Rotate) Private->Offset.Width = HFlip ? 320 - Device->Width : 0;
else Private->Offset.Height = HFlip ? 320 - Device->Height : 0;
if (Rotate) Private->Offset.Width += HFlip ? 320 - Device->Width : 0;
else Private->Offset.Height += HFlip ? 320 - Device->Height : 0;
}
#ifdef SHADOW_BUFFER
@@ -235,7 +235,7 @@ static bool Init( struct GDS_Device* Device ) {
Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
ESP_LOGI(TAG, "ST77xx with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
ESP_LOGI(TAG, "ST77xx with bit depth %u, offsets %hu:%hu, page %u, iRAM %p", Device->Depth, Private->Offset.Height, Private->Offset.Width, Private->PageSize, Private->iRAM);
// Sleepout + Booster
Device->WriteCommand( Device, 0x11 );
@@ -283,8 +283,14 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
*Device = ST77xx;
sscanf(Driver, "%*[^:]:%u", &Depth);
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);
}
if (Depth == 18) {
Device->Mode = GDS_RGB666;

View File

@@ -98,12 +98,14 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
}
}
bool GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font ) {
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font ) {
const struct GDS_FontDef* OldFont = Display->Font;
Display->FontForceProportional = false;
Display->FontForceMonospace = false;
Display->Font = Font;
return true;
return OldFont;
}
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force ) {

View File

@@ -46,7 +46,7 @@ typedef enum {
TextAnchor_Center
} TextAnchor;
bool GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font );
const struct GDS_FontDef* GDS_SetFont( struct GDS_Device* Display, const struct GDS_FontDef* Font );
void GDS_FontForceProportional( struct GDS_Device* Display, bool Force );
void GDS_FontForceMonospace( struct GDS_Device* Display, bool Force );
@@ -59,7 +59,8 @@ int GDS_FontGetMaxCharsPerColumn( struct GDS_Device* Display );
int GDS_FontGetCharWidth( struct GDS_Device* Display, char Character );
int GDS_FontGetCharHeight( struct GDS_Device* Display );
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text );\
int GDS_FontMeasureString( struct GDS_Device* Display, const char* Text );
int GDS_FontMeasureStringLine( struct GDS_Device* Display, int Line, const char* Text );
void GDS_FontDrawChar( struct GDS_Device* Display, char Character, int x, int y, int Color );
void GDS_FontDrawString( struct GDS_Device* Display, int x, int y, const char* Text, int Color );

View File

@@ -15,7 +15,7 @@
#include "gds_private.h"
#include "gds_image.h"
const char TAG[] = "ImageDec";
const static char TAG[] = "ImageDec";
#define SCRATCH_SIZE 3100

View File

@@ -124,6 +124,8 @@ struct GDS_Device {
// may provide for optimization
void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode );
void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
// may provide for tweaking
void (*SPIParams)(int Speed, uint8_t *mode, uint8_t *CS_pre, uint8_t *CS_post);
// interface-specific methods
WriteCommandProc WriteCommand;

View File

@@ -121,6 +121,19 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
return Width + X < Device->Width;
}
/****************************************************************************************
*
*/
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text) {
const struct GDS_FontDef *Font = GDS_SetFont( Device, Device->Lines[N-1].Font );
if (Attr & GDS_TEXT_MONOSPACE) GDS_FontForceMonospace( Device, true );
int Width = GDS_FontMeasureString( Device, Text );
GDS_SetFont( Device, Font );
return Width;
}
/****************************************************************************************
* Try to align string for better scrolling visual. there is probably much better to do
*/

View File

@@ -31,5 +31,6 @@ struct GDS_Device;
bool GDS_TextSetFontAuto(struct GDS_Device* Device, int N, int FontType, int Space);
bool GDS_TextSetFont(struct GDS_Device* Device, int N, const struct GDS_FontDef *Font, int Space);
bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Text);
int GDS_GetTextWidth(struct GDS_Device* Device, int N, int Attr, char *Text);
int GDS_TextStretch(struct GDS_Device* Device, int N, char *String, int Max);
void GDS_TextPos(struct GDS_Device* Device, int FontType, int Where, int Attr, char *Text, ...);

View File

@@ -43,7 +43,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
if (CSPin >= 0) {
ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
}
}
memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) );
@@ -51,7 +51,9 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
SPIDeviceConfig.spics_io_num = CSPin;
SPIDeviceConfig.queue_size = 1;
SPIDeviceConfig.flags = SPI_DEVICE_NO_DUMMY;
if (Device->SPIParams) Device->SPIParams(SPIDeviceConfig.clock_speed_hz, &SPIDeviceConfig.mode,
&SPIDeviceConfig.cs_ena_pretrans, &SPIDeviceConfig.cs_ena_posttrans);
ESP_ERROR_CHECK_NONFATAL( spi_bus_add_device( SPIHost, &SPIDeviceConfig, &SPIDevice ), return false );
Device->WriteCommand = SPIDefaultWriteCommand;
@@ -74,16 +76,23 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
}
static bool SPIDefaultWriteBytes( spi_device_handle_t SPIHandle, int WriteMode, const uint8_t* Data, size_t DataLength ) {
spi_transaction_t SPITransaction = { 0 };
spi_transaction_t SPITransaction = { };
NullCheck( SPIHandle, return false );
NullCheck( Data, return false );
if ( DataLength > 0 ) {
gpio_set_level( DCPin, WriteMode );
SPITransaction.length = DataLength * 8;
SPITransaction.tx_buffer = Data;
if (DataLength <= 4) {
SPITransaction.flags = SPI_TRANS_USE_TXDATA;
SPITransaction.tx_data[0] = *Data++; SPITransaction.tx_data[1] = *Data++;
SPITransaction.tx_data[2] = *Data++; SPITransaction.tx_data[3] = *Data;
} else {
SPITransaction.tx_buffer = Data;
}
// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)
ESP_ERROR_CHECK_NONFATAL( spi_device_polling_transmit(SPIHandle, &SPITransaction), return false );

View File

@@ -41,7 +41,12 @@ static EXT_RAM_ATTR struct {
int offset, boundary;
char *metadata_config;
bool timer, refresh;
uint32_t elapsed, duration;
uint32_t elapsed;
struct {
uint32_t value;
char string[8]; // H:MM:SS
bool visible;
} duration;
TickType_t tick;
} displayer;
@@ -71,11 +76,11 @@ void display_init(char *welcome) {
char *config = config_alloc_get_str("display_config", CONFIG_DISPLAY_CONFIG, "N/A");
int width = -1, height = -1, backlight_pin = -1;
char *p, *drivername = strstr(config, "driver");
char *drivername = strstr(config, "driver");
if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "back")) != NULL) backlight_pin = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "width", '=', width);
PARSE_PARAM(config, "height", '=', height);
PARSE_PARAM(config, "back", '=', backlight_pin);
// query drivers to see if we have a match
ESP_LOGI(TAG, "Trying to configure display with %s", config);
@@ -89,24 +94,24 @@ void display_init(char *welcome) {
// so far so good
if (display && width > 0 && height > 0) {
int RST_pin = -1;
if ((p = strcasestr(config, "reset")) != NULL) RST_pin = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "reset", '=', RST_pin);
// Detect driver interface
if (strstr(config, "I2C") && i2c_system_port != -1) {
if (strcasestr(config, "I2C") && i2c_system_port != -1) {
int address = 0x3C;
if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "address", '=', address);
init = true;
GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
GDS_I2CAttachDevice( display, width, height, address, RST_pin, backlight_pin );
ESP_LOGI(TAG, "Display is I2C on port %u", address);
} else if (strstr(config, "SPI") && spi_system_host != -1) {
} else if (strcasestr(config, "SPI") && spi_system_host != -1) {
int CS_pin = -1, speed = 0;
if ((p = strcasestr(config, "cs")) != NULL) CS_pin = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "speed")) != NULL) speed = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "cs", '=', CS_pin);
PARSE_PARAM(config, "speed", '=', speed);
init = true;
GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
@@ -126,7 +131,7 @@ void display_init(char *welcome) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[DISPLAYER_STACK_SIZE] __attribute__ ((aligned (4)));
GDS_SetLayout( display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
GDS_SetLayout(display, strcasestr(config, "HFlip"), strcasestr(config, "VFlip"), strcasestr(config, "rotate"));
GDS_SetFont(display, &Font_droid_sans_fallback_15x17 );
GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, welcome);
@@ -193,18 +198,34 @@ static void displayer_task(void *args) {
// handler elapsed track time
if (displayer.timer && displayer.state == DISPLAYER_ACTIVE) {
char counter[16];
char line[19] = "-", *_line = line + 1; // [-]H:MM:SS / H:MM:SS
TickType_t tick = xTaskGetTickCount();
uint32_t elapsed = (tick - displayer.tick) * portTICK_PERIOD_MS;
if (elapsed >= 1000) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
displayer.tick = tick;
displayer.elapsed += elapsed / 1000;
xSemaphoreGive(displayer.mutex);
if (displayer.elapsed < 3600) snprintf(counter, 16, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
else snprintf(counter, 16, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, (GDS_TEXT_CLEAR | GDS_TEXT_CLEAR_EOL) | GDS_TEXT_UPDATE, counter);
elapsed = displayer.elapsed += elapsed / 1000;
xSemaphoreGive(displayer.mutex);
// when we have duration but no space, display remaining time
if (displayer.duration.value && !displayer.duration.visible) elapsed = displayer.duration.value - elapsed;
if (elapsed < 3600) sprintf(_line, "%u:%02u", elapsed / 60, elapsed % 60);
else sprintf(_line, "%u:%02u:%02u", (elapsed / 3600) % 100, (elapsed % 3600) / 60, elapsed % 60);
// concatenate if we have room for elapsed / duration
if (displayer.duration.visible) {
strcat(_line, "/");
strcat(_line, displayer.duration.string);
} else if (displayer.duration.value) {
_line--;
}
// just re-write the whole line it's easier
GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR, displayer.header);
GDS_TextLine(display, 1, GDS_TEXT_RIGHT, GDS_TEXT_UPDATE, _line);
timer_sleep = 1000;
} else timer_sleep = max(1000 - elapsed, 0);
} else timer_sleep = DEFAULT_SLEEP;
@@ -279,8 +300,8 @@ void displayer_metadata(char *artist, char *album, char *title) {
}
// get optional scroll speed & pause
if ((p = strcasestr(displayer.metadata_config, "speed")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.speed);
if ((p = strcasestr(displayer.metadata_config, "pause")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.pause);
PARSE_PARAM(displayer.metadata_config, "speed", '=', displayer.speed);
PARSE_PARAM(displayer.metadata_config, "pause", '=', displayer.pause);
displayer.offset = 0;
utf8_decode(displayer.string);
@@ -318,9 +339,27 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
if (elapsed >= 0) displayer.elapsed = elapsed / 1000;
if (duration >= 0) displayer.duration = duration / 1000;
if (displayer.timer) displayer.tick = xTaskGetTickCount();
if (elapsed >= 0) displayer.elapsed = elapsed / 1000;
if (duration > 0) {
displayer.duration.visible = true;
displayer.duration.value = duration / 1000;
if (displayer.duration.value > 3600) sprintf(displayer.duration.string, "%u:%02u:%02u", (displayer.duration.value / 3600) % 10,
(displayer.duration.value % 3600) / 60, displayer.duration.value % 60);
else sprintf(displayer.duration.string, "%u:%02u", displayer.duration.value / 60, displayer.duration.value % 60);
char *buf;
asprintf(&buf, "%s %s/%s", displayer.header, displayer.duration.string, displayer.duration.string);
if (GDS_GetTextWidth(display, 1, 0, buf) > GDS_GetWidth(display)) {
ESP_LOGW(TAG, "Can't fit duration %s (%d) on screen using elapsed only", buf, GDS_GetTextWidth(display, 1, 0, buf));
displayer.duration.visible = false;
}
free(buf);
} else if (!duration) {
displayer.duration.visible = false;
displayer.duration.value = 0;
}
xSemaphoreGive(displayer.mutex);
}
@@ -345,7 +384,8 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
displayer.timer = false;
displayer.refresh = true;
displayer.string[0] = '\0';
displayer.elapsed = displayer.duration = 0;
displayer.elapsed = displayer.duration.value = 0;
displayer.duration.visible = false;
displayer.offset = displayer.boundary = 0;
display_bus(&displayer, DISPLAY_BUS_TAKE);
vTaskResume(displayer.task);

View File

@@ -26,7 +26,7 @@ static const uint8_t Square721_BT11x14[] = {
0x02, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char ,
0x04, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char -
0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char .
0x04, 0x00, 0x0C, 0x80, 0x03, 0x70, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x80, 0x03, 0x70, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char /
0x08, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x03, 0x08, 0x04, 0x08, 0x04, 0x08, 0x04, 0x08, 0x04, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 0
0x08, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x04, 0x08, 0x04, 0xF8, 0x07, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 1
0x08, 0x00, 0x00, 0x00, 0x00, 0x30, 0x07, 0x08, 0x05, 0x88, 0x04, 0x88, 0x04, 0x88, 0x04, 0x70, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Code for char 2

View File

@@ -6,26 +6,26 @@
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "bt_app_core.h"
#include <stdint.h>
#include "esp_system.h"
#include <string.h>
#include <stdbool.h>
#include "esp_log.h"
#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOSConfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_bt_api.h"
#include "bt_app_core.h"
static const char * TAG = "btappcore";
static const char *TAG = "btappcore";
static void bt_app_task_handler(void *arg);
static bool bt_app_send_msg(bt_app_msg_t *msg);
static void bt_app_work_dispatched(bt_app_msg_t *msg);
static xQueueHandle s_bt_app_task_queue = NULL;
static xTaskHandle s_bt_app_task_handle = NULL;
static xQueueHandle s_bt_app_task_queue;
static bool running;
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback)
{
@@ -77,9 +77,49 @@ static void bt_app_work_dispatched(bt_app_msg_t *msg)
static void bt_app_task_handler(void *arg)
{
bt_app_msg_t msg;
for (;;) {
esp_err_t err;
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
ESP_LOGE(TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
ESP_LOGE(TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
goto exit;
}
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch((bt_av_hdl_stack_evt_t*) arg, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
#if (CONFIG_BT_SSP_ENABLED)
/* Set default parameters for Secure Simple Pairing */
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif
running = true;
while (running) {
if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) {
ESP_LOGV(TAG,"%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event);
switch (msg.sig) {
case BT_APP_SIG_WORK_DISPATCH:
bt_app_work_dispatched(&msg);
@@ -87,36 +127,46 @@ static void bt_app_task_handler(void *arg)
default:
ESP_LOGW(TAG,"%s, unhandled sig: %d", __func__, msg.sig);
break;
} // switch (msg.sig)
}
if (msg.param) {
free(msg.param);
}
}
else
{
} else {
ESP_LOGW(TAG,"No messaged received from queue.");
}
}
ESP_LOGD(TAG, "bt_app_task shutting down");
if (esp_bluedroid_disable() != ESP_OK) goto exit;
// this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and
// if we don't wait for it then disable crashes... don't know why
vTaskDelay(2*200 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "esp_bluedroid_disable called successfully");
if (esp_bluedroid_deinit() != ESP_OK) goto exit;
ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully");
if (esp_bt_controller_disable() != ESP_OK) goto exit;
ESP_LOGD(TAG, "esp_bt_controller_disable called successfully");
if (esp_bt_controller_deinit() != ESP_OK) goto exit;
ESP_LOGD(TAG, "bt stopped successfully");
exit:
vQueueDelete(s_bt_app_task_queue);
running = false;
vTaskDelete(NULL);
}
void bt_app_task_start_up(void)
void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler)
{
s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t));
assert(s_bt_app_task_queue!=NULL);
assert(xTaskCreate(bt_app_task_handler, "BtAppT", 4096, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle)==pdPASS);
return;
xTaskCreate(bt_app_task_handler, "BtAppT", 4096, handler, configMAX_PRIORITIES - 3, NULL);
}
void bt_app_task_shut_down(void)
{
if (s_bt_app_task_handle) {
vTaskDelete(s_bt_app_task_handle);
s_bt_app_task_handle = NULL;
}
if (s_bt_app_task_queue) {
vQueueDelete(s_bt_app_task_queue);
s_bt_app_task_queue = NULL;
}
running = false;
}

View File

@@ -17,6 +17,10 @@
#define BT_APP_CORE_TAG "BT_APP_CORE"
#define BT_APP_SIG_WORK_DISPATCH (0x01)
enum {
BT_APP_EVT_STACK_UP = 0,
};
/* A2DP global state */
enum {
APP_AV_STATE_IDLE,
@@ -56,12 +60,17 @@ typedef struct {
*/
typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src);
/**
* @brief callback for startup event
*/
typedef void bt_av_hdl_stack_evt_t(uint16_t event, void *p_param);
/**
* @brief work dispatcher for the application task
*/
bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback);
void bt_app_task_start_up(void);
void bt_app_task_start_up(bt_av_hdl_stack_evt_t* handler);
void bt_app_task_shut_down(void);

View File

@@ -37,19 +37,15 @@
#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3)
#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4)
#define BT_AV_TAG "BT_AV"
#define BT_RC_TG_TAG "RCTG"
#define BT_RC_CT_TAG "RCCT"
static const char BT_AV_TAG[] = "BT_AV";
static const char BT_RC_TG_TAG[] = "RCTG";
static const char BT_RC_CT_TAG[] = "RCCT";
#ifndef CONFIG_BT_NAME
#define CONFIG_BT_NAME "ESP32-BT"
#endif
/* event for handler "bt_av_hdl_stack_up */
enum {
BT_APP_EVT_STACK_UP = 0,
};
char * bt_name = NULL;
static char * bt_name = NULL;
static bool (*bt_app_a2d_cmd_cb)(bt_sink_cmd_t cmd, ...);
static void (*bt_app_a2d_data_cb)(const uint8_t *data, uint32_t len);
@@ -566,48 +562,13 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
{
esp_err_t err;
bt_app_a2d_cmd_cb = cmd_handler;
cmd_handler_chain = cmd_cb;
bt_app_a2d_data_cb = data_cb;
esp_bt_controller_mem_release(ESP_BT_MODE_BLE);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_init()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err));
return;
}
/* create application task */
bt_app_task_start_up();
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
#if (CONFIG_BT_SSP_ENABLED == true)
/* Set default parameters for Secure Simple Pairing */
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif
// create task and run event loop
bt_app_task_start_up(bt_av_hdl_stack_evt);
char *item = config_alloc_get_default(NVS_TYPE_STR, "bt_sink_volume", "127", 0);
sink_volume = atol(item);
free(item);
@@ -636,33 +597,17 @@ void bt_sink_init(bt_cmd_vcb_t cmd_cb, bt_data_cb_t data_cb)
bError=true;
}
esp_pin_code[i]= pin_code[i];
}
if(bError){
esp_pin_code[0]='1';
esp_pin_code[1]='2';
esp_pin_code[2]='3';
esp_pin_code[3]='4';
}
esp_bt_gap_set_pin(pin_type, strlen(pin_code), esp_pin_code);
if (bError) memcpy(esp_pin_code, "1234", 4);
esp_bt_gap_set_pin(pin_type, strlen(pin_code), esp_pin_code);
free(pin_code);
}
void bt_sink_deinit(void)
{
bt_app_task_shut_down();
ESP_LOGD(BT_AV_TAG, "bt_app_task shutdown successfully");
if (esp_bluedroid_disable() != ESP_OK) return;
// this disable has a sleep timer BTA_DISABLE_DELAY in bt_target.h and
// if we don't wait for it then disable crashes... don't know why
vTaskDelay(2*200 / portTICK_PERIOD_MS);
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_disable called successfully");
if (esp_bluedroid_deinit() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
if (esp_bt_controller_disable() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "esp_bt_controller_disable called successfully");
if (esp_bt_controller_deinit() != ESP_OK) return;
ESP_LOGD(BT_AV_TAG, "bt stopped successfully");
}
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)

View File

@@ -17,9 +17,9 @@
#include "freertos/timers.h"
#include "argtable3/argtable3.h"
#include "platform_config.h"
#include "trace.h"
#include "messaging.h"
#include "cJSON.h"
#include "trace.h"
static const char * TAG = "bt_app_source";
static const char * BT_RC_CT_TAG="RCCT";
@@ -31,11 +31,6 @@ extern bool output_stopped(void);
static void bt_app_av_state_connecting(uint16_t event, void *param);
static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param);
/* event for handler "bt_av_hdl_stack_up */
enum {
BT_APP_EVT_STACK_UP = 0,
};
char * APP_AV_STATE_DESC[] = {
"APP_AV_STATE_IDLE",
"APP_AV_STATE_DISCOVERING",
@@ -104,8 +99,6 @@ static int s_connecting_intv = 0;
cJSON * peers_list=NULL;
static struct {
// int control_delay;
// int connect_timeout_delay;
char * sink_name;
} squeezelite_conf;
@@ -121,6 +114,7 @@ static cJSON * peers_list_get_entry(const char * s_peer_bdname){
ESP_LOGV(TAG,"Entry name %s NOT found in current scan list", s_peer_bdname);
return NULL;
}
static void peers_list_reset(){
cJSON * element=NULL;
cJSON_ArrayForEach(element,peers_list){
@@ -131,6 +125,7 @@ static void peers_list_reset(){
}
}
}
static void peers_list_purge(){
cJSON * element=NULL;
cJSON_ArrayForEach(element,peers_list){
@@ -143,12 +138,14 @@ static void peers_list_purge(){
}
}
}
static cJSON * peers_list_create_entry(const char * s_peer_bdname, int32_t rssi){
cJSON * entry = cJSON_CreateObject();
cJSON_AddStringToObject(entry,"name",s_peer_bdname);
cJSON_AddNumberToObject(entry,"rssi",rssi);
return entry;
}
static void peers_list_update_add(const char * s_peer_bdname, int32_t rssi){
cJSON * element= peers_list_get_entry(s_peer_bdname);
if(element){
@@ -165,6 +162,7 @@ static void peers_list_update_add(const char * s_peer_bdname, int32_t rssi){
cJSON_AddItemToArray(peers_list,element);
}
}
static void peers_list_maintain(const char * s_peer_bdname, int32_t rssi){
if(!peers_list){
ESP_LOGV(TAG,"Initializing BT peers list");
@@ -194,35 +192,35 @@ int bt_app_source_get_a2d_state(){
ESP_LOGD(TAG,"a2dp status: %u = %s", bt_app_source_a2d_state, APP_AV_STATE_DESC[bt_app_source_a2d_state]);
return bt_app_source_a2d_state;
}
int bt_app_source_get_media_state(){
ESP_LOGD(TAG,"media state : %u ", bt_app_source_media_state);
return bt_app_source_media_state;
}
void set_app_source_state(int new_state){
if(bt_app_source_a2d_state!=new_state){
ESP_LOGD(TAG, "Updating state from %s to %s", APP_AV_STATE_DESC[bt_app_source_a2d_state], APP_AV_STATE_DESC[new_state]);
bt_app_source_a2d_state=new_state;
}
}
void set_a2dp_media_state(int new_state){
if(bt_app_source_media_state!=new_state){
bt_app_source_media_state=new_state;
}
}
void hal_bluetooth_init(const char * options)
{
struct {
struct arg_str *sink_name;
// struct arg_int *control_delay;
// struct arg_int *connect_timeout_delay;
struct arg_end *end;
} squeezelite_args;
ESP_LOGD(TAG,"Initializing Bluetooth HAL");
squeezelite_args.sink_name = arg_str0("n", "name", "<sink name>", "the name of the bluetooth to connect to");
// squeezelite_args.control_delay = arg_int0("d", "delay", "<control delay>", "the delay between each pass at the A2DP control loop");
// squeezelite_args.connect_timeout_delay = arg_int0("t","timeout", "<timeout>", "the timeout duration for connecting to the A2DP sink");
squeezelite_args.end = arg_end(2);
ESP_LOGD(TAG,"Copying parameters");
@@ -265,83 +263,17 @@ void hal_bluetooth_init(const char * options)
}
}
// if(squeezelite_args.connect_timeout_delay->count == 0)
// {
// ESP_LOGD(TAG,"Using default connect timeout");
// char * p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_ctmt", NULL, 0);
// if(p){
// squeezelite_conf.connect_timeout_delay=atoi(p);
// free(p);
// }
// else {
// squeezelite_conf.connect_timeout_delay=CONFIG_A2DP_CONNECT_TIMEOUT_MS;
// }
// } else {
// squeezelite_conf.connect_timeout_delay=squeezelite_args.connect_timeout_delay->ival[0];
// }
// if(squeezelite_args.control_delay->count == 0)
// {
// ESP_LOGD(TAG,"Using default control delay");
// char * p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_ctrld", NULL, 0);
// if(p){
// squeezelite_conf.control_delay=atoi(p);
// free(p);
// }
// else {
// squeezelite_conf.control_delay=CONFIG_A2DP_CONNECT_TIMEOUT_MS;
// }
// } else {
// squeezelite_conf.control_delay=squeezelite_args.control_delay->ival[0];
// }
ESP_LOGD(TAG,"Freeing options");
free(argv);
free(opts);
/*
* Bluetooth audio source init Start
*/
//running_test = false;
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if (esp_bt_controller_init(&bt_cfg) != ESP_OK) {
ESP_LOGE(TAG,"%s initialize controller failed\n", __func__);
return;
}
if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) {
ESP_LOGE(TAG,"%s enable controller failed\n", __func__);
return;
}
if (esp_bluedroid_init() != ESP_OK) {
ESP_LOGE(TAG,"%s initialize bluedroid failed\n", __func__);
return;
}
if (esp_bluedroid_enable() != ESP_OK) {
ESP_LOGE(TAG,"%s enable bluedroid failed\n", __func__);
return;
}
/* create application task */
bt_app_task_start_up();
/* Bluetooth device name, connection mode and profile set up */
bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL);
#if (CONFIG_BT_SSP_ENABLED == true)
/* Set default parameters for Secure Simple Pairing */
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO;
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
#endif
// create task and run event loop
bt_app_task_start_up(bt_av_hdl_stack_evt);
/*
* Set default parameters for Legacy Pairing
* Use variable pin, input pin code when pairing
*/
*/
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
esp_bt_pin_code_t pin_code;
esp_bt_gap_set_pin(pin_type, 0, pin_code);
@@ -349,23 +281,14 @@ void hal_bluetooth_init(const char * options)
}
void hal_bluetooth_stop(void) {
/* this still does not work, can't figure out how to stop properly this BT stack */
bt_app_task_shut_down();
ESP_LOGI(TAG, "bt_app_task shutdown successfully");
if (esp_bluedroid_disable() != ESP_OK) return;
ESP_LOGI(TAG, "esp_bluedroid_disable called successfully");
if (esp_bluedroid_deinit() != ESP_OK) return;
ESP_LOGI(TAG, "esp_bluedroid_deinit called successfully");
if (esp_bt_controller_disable() != ESP_OK) return;
ESP_LOGI(TAG, "esp_bt_controller_disable called successfully");
if (esp_bt_controller_deinit() != ESP_OK) return;
ESP_LOGI(TAG, "bt stopped successfully");
}
static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param)
{
bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL);
}
static void handle_bt_gap_pin_req(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param){
char * pin_str = config_alloc_get_default(NVS_TYPE_STR, "a2dp_spin", "0000", 0);
int pinlen=pin_str?strlen(pin_str):0;
@@ -401,6 +324,7 @@ static void handle_bt_gap_pin_req(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_par
}
FREE_AND_NULL(pin_str);
}
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
{
@@ -410,28 +334,21 @@ static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *pa
break;
}
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED)
{
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_PURGE);
if (bt_app_source_a2d_state == APP_AV_STATE_DISCOVERED)
{
ESP_LOGI(TAG,"Discovery completed. Ready to start connecting to %s. ",s_peer_bdname);
set_app_source_state(APP_AV_STATE_UNCONNECTED);
}
else
{
if (bt_app_source_a2d_state == APP_AV_STATE_DISCOVERED) {
set_app_source_state(APP_AV_STATE_CONNECTING);
ESP_LOGI(TAG,"Discovery completed. Ready to start connecting to %s. ", s_peer_bdname);
esp_a2d_source_connect(s_peer_bda);
} else {
// not discovered, continue to discover
ESP_LOGI(TAG,"Device discovery failed, continue to discover...");
ESP_LOGI(TAG, "Device discovery failed, continue to discover...");
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
}
}
else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
ESP_LOGI(TAG,"Discovery started.");
peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_RESET);
}
else
{
ESP_LOGD(TAG,"This shouldn't happen. Discovery has only 2 states (for now).");
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
ESP_LOGI(TAG, "Discovery started.");
peers_list_maintain(NULL, PEERS_LIST_MAINTAIN_RESET);
}
break;
}
@@ -519,17 +436,18 @@ static const char * conn_state_str(esp_a2d_connection_state_t state){
}
return statestr;
}
static void unexpected_connection_state(int from, esp_a2d_connection_state_t to)
{
ESP_LOGW(TAG,"Unexpected connection state change. App State: %s (%u) Connection State %s (%u)", APP_AV_STATE_DESC[from], from,conn_state_str(to), to);
}
static void handle_connect_state_unconnected(uint16_t event, esp_a2d_cb_param_t *param){
ESP_LOGV(TAG, "A2DP Event while unconnected ");
switch (param->conn_stat.state)
{
case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state);
set_app_source_state(APP_AV_STATE_UNCONNECTED);
break;
case ESP_A2D_CONNECTION_STATE_CONNECTING:
unexpected_connection_state(bt_app_source_a2d_state, param->conn_stat.state);
@@ -549,17 +467,10 @@ static void handle_connect_state_unconnected(uint16_t event, esp_a2d_cb_param_t
}
}
static void handle_connect_state_connecting(uint16_t event, esp_a2d_cb_param_t *param){
ESP_LOGV(TAG, "A2DP connection state event : %s ",conn_state_str(param->conn_stat.state));
// a2d = (esp_a2d_cb_param_t *)(param);
// if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) {
// ESP_LOGI(BT_AV_TAG, "a2dp connected");
// s_a2d_state = APP_AV_STATE_CONNECTED;
// s_media_state = APP_AV_MEDIA_STATE_IDLE;
// esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
// } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) {
// s_a2d_state = APP_AV_STATE_UNCONNECTED;
// }
switch (param->conn_stat.state)
{
case ESP_A2D_CONNECTION_STATE_DISCONNECTED:
@@ -571,9 +482,8 @@ static void handle_connect_state_connecting(uint16_t event, esp_a2d_cb_param_t *
}
set_app_source_state(APP_AV_STATE_UNCONNECTED);
break;
case ESP_A2D_CONNECTION_STATE_CONNECTING: {
case ESP_A2D_CONNECTION_STATE_CONNECTING:
break;
}
case ESP_A2D_CONNECTION_STATE_CONNECTED:
set_app_source_state(APP_AV_STATE_CONNECTED);
set_a2dp_media_state(APP_AV_MEDIA_STATE_IDLE);
@@ -764,8 +674,6 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
return;
}
/* search for device named "ESP_SPEAKER" in its extended inqury response */
if (eir) {
ESP_LOGV(TAG,"--Getting details from eir.\n");
get_name_from_eir(eir, s_peer_bdname, NULL);
@@ -777,27 +685,16 @@ static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param)
if (squeezelite_conf.sink_name && strlen(squeezelite_conf.sink_name) >0 && strcmp((char *)s_peer_bdname, squeezelite_conf.sink_name) == 0) {
ESP_LOGI(TAG,"Found our target device. address %s, name %s", bda_str, s_peer_bdname);
ESP_LOGV(TAG,"=======================\n");
if(esp_bt_gap_cancel_discovery()!=ESP_ERR_INVALID_STATE)
{
ESP_LOGD(TAG,"Cancel device discovery ...");
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
set_app_source_state(APP_AV_STATE_DISCOVERED);
}
else
{
ESP_LOGE(TAG,"Cancel device discovery failed...");
}
}
else
{
memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
set_app_source_state(APP_AV_STATE_DISCOVERED);
esp_bt_gap_cancel_discovery();
} else {
ESP_LOGV(TAG,"Not the device we are looking for (%s). Continuing scan", squeezelite_conf.sink_name?squeezelite_conf.sink_name:"N/A");
}
}
static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
{
switch (event) {
case BT_APP_EVT_STACK_UP: {
ESP_LOGI(TAG,"BT Stack going up.");
@@ -956,7 +853,6 @@ static void bt_app_av_state_unconnected(uint16_t event, void *param)
break;
case ESP_A2D_AUDIO_STATE_EVT:
ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_STATE_EVT));
break;
case ESP_A2D_AUDIO_CFG_EVT:
ESP_LOG_DEBUG_EVENT(TAG,QUOTE(ESP_A2D_AUDIO_CFG_EVT));

View File

@@ -0,0 +1,10 @@
idf_build_get_property(prefix IDF_PATH)
string(CONCAT prefix "${prefix}" "/components/esp_http_server")
idf_component_register(
SRC_DIRS "${prefix}/src" "${prefix}/src/util"
INCLUDE_DIRS "${prefix}/include"
PRIV_INCLUDE_DIRS "." "${prefix}/src/port/esp32" "${prefix}/src/util"
REQUIRES nghttp # for http_parser.h
PRIV_REQUIRES lwip
)

View File

@@ -0,0 +1 @@
source "$IDF_PATH/components/esp_http_server/Kconfig"

View File

@@ -0,0 +1,68 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _OSAL_H_
#define _OSAL_H_
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <unistd.h>
#include <stdint.h>
#include <esp_timer.h>
#ifdef __cplusplus
extern "C" {
#endif
#define OS_SUCCESS ESP_OK
#define OS_FAIL ESP_FAIL
typedef TaskHandle_t othread_t;
static inline int httpd_os_thread_create(othread_t *thread,
const char *name, uint16_t stacksize, int prio,
void (*thread_routine)(void *arg), void *arg,
BaseType_t core_id)
{
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
if (*thread) {
return OS_SUCCESS;
}
return OS_FAIL;
}
/* Only self delete is supported */
static inline void httpd_os_thread_delete(void)
{
vTaskDelete(xTaskGetCurrentTaskHandle());
}
static inline void httpd_os_thread_sleep(int msecs)
{
vTaskDelay(msecs / portTICK_RATE_MS);
}
static inline othread_t httpd_os_thread_handle(void)
{
return xTaskGetCurrentTaskHandle();
}
#ifdef __cplusplus
}
#endif
#endif /* ! _OSAL_H_ */

View File

@@ -1,6 +1,6 @@
idf_component_register( SRC_DIRS .
INCLUDE_DIRS .
PRIV_REQUIRES tools newlib console esp_common freertos
PRIV_REQUIRES tools newlib console esp_common freertos
REQUIRES nvs_flash json
)

View File

@@ -8,9 +8,20 @@
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#define PARSE_PARAM(S,P,C,V) do { \
char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); \
} while (0)
#define PARSE_PARAM_STR(S,P,C,V,I) do { \
char *__p; \
if ((__p = strstr(S, P)) && (__p = strchr(__p, C))) { \
while (*++__p == ' '); \
sscanf(__p,"%" #I "[^,]", V); \
} \
} while (0)
#define DECLARE_SET_DEFAULT(t) void config_set_default_## t (const char *key, t value);
#define DECLARE_GET_NUM(t) esp_err_t config_get_## t (const char *key, t * value);
#ifndef FREE_RESET
@@ -45,3 +56,7 @@ esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, const void * va
nvs_type_t config_get_item_type(cJSON * entry);
void * config_safe_alloc_get_entry_value(nvs_type_t nvs_type, cJSON * entry);
#ifdef __cplusplus
}
#endif

View File

@@ -3,7 +3,6 @@
#include "application_name.h"
#include "esp_log.h"
#include "esp_console.h"
#include "esp_pthread.h"
#include "../cmd_system.h"
#include "argtable3/argtable3.h"
#include "freertos/FreeRTOS.h"
@@ -14,9 +13,7 @@
#include "esp_app_format.h"
extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length);
static const char * TAG = "squeezelite_cmd";
#define SQUEEZELITE_THREAD_STACK_SIZE (4*1024)
#define SQUEEZELITE_THREAD_STACK_SIZE (8*1024)
const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
@@ -42,8 +39,7 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
extern int main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);
pthread_t thread_squeezelite;
pthread_t thread_squeezelite_runner;
/** Arguments used by 'squeezelite' function */
static struct {
struct arg_str *parameters;
@@ -53,57 +49,47 @@ static struct {
int argc;
char ** argv;
} thread_parms ;
static void * squeezelite_runner_thread(){
ESP_LOGI(TAG ,"Calling squeezelite");
main(thread_parms.argc,thread_parms.argv);
return NULL;
}
#define ADDITIONAL_SQUEEZELITE_ARGS 5
static void * squeezelite_thread(){
int * exit_code;
static bool isRunning=false;
if(isRunning) {
ESP_LOGE(TAG,"Squeezelite already running. Exiting!");
return NULL;
}
isRunning=true;
static void squeezelite_thread(void *arg){
ESP_LOGV(TAG ,"Number of args received: %u",thread_parms.argc );
ESP_LOGV(TAG ,"Values:");
for(int i = 0;i<thread_parms.argc; i++){
ESP_LOGV(TAG ," %s",thread_parms.argv[i]);
}
ESP_LOGV(TAG,"Starting Squeezelite runner Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite-run";
cfg.inherit_cfg = true;
cfg.stack_size = SQUEEZELITE_THREAD_STACK_SIZE ;
esp_pthread_set_cfg(&cfg);
// no attribute if we want esp stack stack to prevail
pthread_create(&thread_squeezelite_runner, NULL, squeezelite_runner_thread,NULL);
// Wait for thread completion so we can free up memory.
pthread_join(thread_squeezelite_runner,(void **)&exit_code);
ESP_LOGI(TAG ,"Calling squeezelite");
main(thread_parms.argc,thread_parms.argv);
ESP_LOGV(TAG ,"Exited from squeezelite's main(). Freeing argv structure.");
for(int i=0;i<thread_parms.argc;i++){
ESP_LOGV(TAG ,"Freeing char buffer for parameter %u", i+1);
free(thread_parms.argv[i]);
}
ESP_LOGV(TAG ,"Freeing argv pointer");
free(thread_parms.argv);
isRunning=false;
ESP_LOGE(TAG, "Exited from squeezelite thread, something's wrong ... rebooting (wait 30s for user to take action)");
if(!wait_for_commit()){
ESP_LOGW(TAG,"Unable to commit configuration. ");
}
vTaskDelay( pdMS_TO_TICKS( 30*1000 ) );
esp_restart();
return NULL;
}
static int launchsqueezelite(int argc, char **argv)
{
static int launchsqueezelite(int argc, char **argv) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
static EXT_RAM_ATTR StackType_t xStack[SQUEEZELITE_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
static bool isRunning = false;
if (isRunning) {
ESP_LOGE(TAG,"Squeezelite already running. Exiting!");
return -1;
}
ESP_LOGV(TAG ,"Begin");
isRunning = true;
ESP_LOGV(TAG, "Parameters:");
for(int i = 0;i<argc; i++){
@@ -128,16 +114,14 @@ static int launchsqueezelite(int argc, char **argv)
}
ESP_LOGD(TAG,"Starting Squeezelite Thread");
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "squeezelite";
cfg.inherit_cfg = true;
esp_pthread_set_cfg(&cfg);
pthread_create(&thread_squeezelite, NULL, squeezelite_thread,NULL);
xTaskCreateStaticPinnedToCore(squeezelite_thread, "squeezelite", SQUEEZELITE_THREAD_STACK_SIZE,
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT, xStack, &xTaskBuffer, CONFIG_PTHREAD_TASK_CORE_DEFAULT);
ESP_LOGD(TAG ,"Back to console thread!");
return 0;
}
void register_squeezelite(){
void register_squeezelite() {
squeezelite_args.parameters = arg_str0(NULL, NULL, "<parms>", "command line for squeezelite. -h for help, --defaults to launch with default values.");
squeezelite_args.end = arg_end(1);
const esp_console_cmd_t launch_squeezelite = {
@@ -148,10 +132,9 @@ void register_squeezelite(){
.argtable = &squeezelite_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&launch_squeezelite) );
}
esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length)
{
esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length) {
if(!bin_url){
ESP_LOGE(TAG,"missing URL parameter. Unable to start OTA");
return ESP_ERR_INVALID_ARG;
@@ -169,5 +152,4 @@ esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length)
ESP_LOGW(TAG, "Rebooting to recovery to complete the installation");
return guided_factory();
return ESP_OK;
}

View File

@@ -30,13 +30,21 @@
#include "messaging.h"
#include "config.h"
pthread_t thread_console;
static pthread_t thread_console;
static void * console_thread();
void console_start();
static const char * TAG = "console";
extern bool bypass_wifi_manager;
extern void register_squeezelite();
static EXT_RAM_ATTR QueueHandle_t uart_queue;
static EXT_RAM_ATTR struct {
uint8_t _buf[128];
StaticRingbuffer_t _ringbuf;
RingbufHandle_t handle;
QueueSetHandle_t queue_set;
} stdin_redir;
/* Prompt to be printed before each line.
* This can be customized, made dynamic, etc.
*/
@@ -50,7 +58,7 @@ const char* recovery_prompt = LOG_COLOR_E "recovery-squeezelite-esp32> " LOG_RES
#define MOUNT_PATH "/data"
#define HISTORY_PATH MOUNT_PATH "/history.txt"
esp_err_t run_command(char * line);
static esp_err_t run_command(char * line);
#define ADD_TO_JSON(o,t,n) if (t->n) cJSON_AddStringToObject(o,QUOTE(n),t->n);
#define ADD_PARMS_TO_CMD(o,t,n) { cJSON * parms = ParmsToJSON(&t.n->hdr); if(parms) cJSON_AddItemToObject(o,QUOTE(n),parms); }
cJSON * cmdList;
@@ -230,13 +238,40 @@ void process_autoexec(){
}
}
static ssize_t stdin_read(int fd, void* data, size_t size) {
size_t bytes = -1;
while (1) {
QueueSetMemberHandle_t activated = xQueueSelectFromSet(stdin_redir.queue_set, portMAX_DELAY);
if (activated == uart_queue) {
uart_event_t event;
xQueueReceive(uart_queue, &event, 0);
if (event.type == UART_DATA) {
bytes = uart_read_bytes(CONFIG_ESP_CONSOLE_UART_NUM, data, size < event.size ? size : event.size, 0);
// we have to do our own line ending translation here
for (int i = 0; i < bytes; i++) if (((char*)data)[i] == '\r') ((char*)data)[i] = '\n';
break;
}
} else if (xRingbufferCanRead(stdin_redir.handle, activated)) {
char *p = xRingbufferReceiveUpTo(stdin_redir.handle, &bytes, 0, size);
// we might receive strings, replace null by \n
for (int i = 0; i < bytes; i++) if (p[i] == '\0' || p[i] == '\r') p[i] = '\n';
memcpy(data, p, bytes);
vRingbufferReturnItem(stdin_redir.handle, p);
break;
}
}
return bytes;
}
static int stdin_dummy(const char * path, int flags, int mode) { return 0; }
void initialize_console() {
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed (unused if we redirect stdin) */
esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
/* Move the caret to the beginning of the next line on '\n' */
esp_vfs_dev_uart_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
@@ -245,16 +280,33 @@ void initialize_console() {
* correct while APB frequency is changing in light sleep mode.
*/
const uart_config_t uart_config = { .baud_rate =
CONFIG_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS,
CONFIG_ESP_CONSOLE_UART_BAUDRATE, .data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1,
.use_ref_tick = true };
ESP_ERROR_CHECK(uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config));
/* Install UART driver for interrupt-driven reads and writes */
ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0));
ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 3, &uart_queue, 0));
/* Tell VFS to use UART driver */
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
/* re-direct stdin to our own driver so we can gather data from various sources */
stdin_redir.queue_set = xQueueCreateSet(2);
stdin_redir.handle = xRingbufferCreateStatic(sizeof(stdin_redir._buf), RINGBUF_TYPE_BYTEBUF, stdin_redir._buf, &stdin_redir._ringbuf);
xRingbufferAddToQueueSetRead(stdin_redir.handle, stdin_redir.queue_set);
xQueueAddToSet(uart_queue, stdin_redir.queue_set);
esp_vfs_t vfs = { };
vfs.flags = ESP_VFS_FLAG_DEFAULT;
vfs.open = stdin_dummy;
vfs.read = stdin_read;
ESP_ERROR_CHECK(esp_vfs_register("/dev/console", &vfs, NULL));
freopen("/dev/console", "r", stdin);
/* Disable buffering on stdin */
setvbuf(stdin, NULL, _IONBF, 0);
/* Initialize the console */
esp_console_config_t console_config = { .max_cmdline_args = 28,
@@ -282,20 +334,14 @@ void initialize_console() {
//linenoiseHistoryLoad(HISTORY_PATH);
}
bool console_push(const char *data, size_t size) {
return xRingbufferSend(stdin_redir.handle, data, size, pdMS_TO_TICKS(100)) == pdPASS;
}
void console_start() {
if(!is_serial_suppressed()){
initialize_console();
}
else {
/* Initialize the console */
esp_console_config_t console_config = { .max_cmdline_args = 28,
.max_cmdline_length = 600,
#if CONFIG_LOG_COLORS
.hint_color = atoi(LOG_COLOR_CYAN)
#endif
};
ESP_ERROR_CHECK(esp_console_init(&console_config));
}
/* we always run console b/c telnet sends commands to stdin */
initialize_console();
/* Register commands */
esp_console_register_help_command();
register_system();
@@ -311,65 +357,56 @@ void console_start() {
}
register_i2ctools();
if(!is_serial_suppressed()){
printf("\n");
if(is_recovery_running){
printf("****************************************************************\n"
"RECOVERY APPLICATION\n"
"This mode is used to flash Squeezelite into the OTA partition\n"
"****\n\n");
}
printf("Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n"
"\n");
if(!is_recovery_running){
printf("To automatically execute lines at startup:\n"
"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n");
}
printf("\n\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n****************************\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n"
"****************************\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
if(is_recovery_running){
recovery_prompt= "recovery-squeezelite-esp32>";
}
prompt = "squeezelite-esp32> ";
#endif //CONFIG_LOG_COLORS
}
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "console";
cfg.inherit_cfg = true;
if(is_recovery_running){
prompt = recovery_prompt;
cfg.stack_size = 4096 ;
}
esp_pthread_set_cfg(&cfg);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&thread_console, &attr, console_thread, NULL);
pthread_attr_destroy(&attr);
}
else if(!is_recovery_running){
process_autoexec();
printf("\n");
if(is_recovery_running){
printf("****************************************************************\n"
"RECOVERY APPLICATION\n"
"This mode is used to flash Squeezelite into the OTA partition\n"
"****\n\n");
}
printf("Type 'help' to get the list of commands.\n"
"Use UP/DOWN arrows to navigate through command history.\n"
"Press TAB when typing command name to auto-complete.\n"
"\n");
if(!is_recovery_running){
printf("To automatically execute lines at startup:\n"
"\tSet NVS variable autoexec (U8) = 1 to enable, 0 to disable automatic execution.\n"
"\tSet NVS variable autoexec[1~9] (string)to a command that should be executed automatically\n");
}
printf("\n\n");
/* Figure out if the terminal supports escape sequences */
int probe_status = linenoiseProbe();
if (probe_status) { /* zero indicates success */
printf("\n****************************\n"
"Your terminal application does not support escape sequences.\n"
"Line editing and history features are disabled.\n"
"On Windows, try using Putty instead.\n"
"****************************\n");
linenoiseSetDumbMode(1);
#if CONFIG_LOG_COLORS
/* Since the terminal doesn't support escape sequences,
* don't use color codes in the prompt.
*/
if(is_recovery_running){
recovery_prompt= "recovery-squeezelite-esp32>";
}
prompt = "squeezelite-esp32> ";
#endif //CONFIG_LOG_COLORS
}
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name= "console";
cfg.inherit_cfg = true;
cfg.stack_size = 4*1024;
if(is_recovery_running){
prompt = recovery_prompt;
}
esp_pthread_set_cfg(&cfg);
pthread_create(&thread_console, NULL, console_thread, NULL);
}
esp_err_t run_command(char * line){
static esp_err_t run_command(char * line){
/* Try to run the command */
int ret;
esp_err_t err = esp_console_run(line, &ret);
@@ -389,6 +426,7 @@ esp_err_t run_command(char * line){
}
return err;
}
static void * console_thread() {
if(!is_recovery_running){
process_autoexec();

View File

@@ -111,7 +111,7 @@ extern char private_key[];
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len);
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
struct raop_ctx_s *raop_create(uint32_t host, char *name,
unsigned char mac[6], int latency,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb) {
@@ -150,7 +150,7 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
memset(ctx, 0, sizeof(raop_ctx_t));
#ifdef WIN32
ctx->svr = glmDNSServer;
ctx->svr = glmDNSServer;
#endif
ctx->host.s_addr = host;
ctx->sock = socket(AF_INET, SOCK_STREAM, 0);
@@ -162,7 +162,7 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
free(ctx);
return NULL;
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = host;
addr.sin_family = AF_INET;
@@ -196,7 +196,8 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
id[63] = '\0';
ctx->svc = mdnsd_register_svc(ctx->svr, id, "_raop._tcp.local", ctx->port, NULL, (const char**) txt);
pthread_create(&ctx->thread, NULL, &rtsp_thread, ctx);
#else
LOG_INFO("starting mDNS with %s", id);
ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
@@ -255,7 +256,7 @@ void raop_delete(struct raop_ctx_s *ctx) {
// brute-force exit of accept()
shutdown(ctx->sock, SHUT_RDWR);
closesocket(ctx->sock);
closesocket(ctx->sock);
// wait to make sure LWIP if scheduled (avoid issue with NotifyTake)
vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -518,22 +519,26 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
#ifdef WIN32
ctx->active_remote.handle = init_mDNS(false, ctx->host);
ctx->active_remote.handle = init_mDNS(false, ctx->host);
pthread_create(&ctx->active_remote.thread, NULL, &search_remote, ctx);
#else
ctx->active_remote.running = true;
ctx->active_remote.destroy_mutex = xSemaphoreCreateBinary();
ctx->active_remote.xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->active_remote.thread = xTaskCreateStaticPinnedToCore( (TaskFunction_t) search_remote, "search_remote", SEARCH_STACK_SIZE, ctx,
ESP_TASK_PRIO_MIN + 2, ctx->active_remote.xStack, ctx->active_remote.xTaskBuffer,
CONFIG_PTHREAD_TASK_CORE_DEFAULT );
#endif
} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
} else if (!strcmp(method, "SETUP") && ((buf = kd_lookup(headers, "Transport")) != NULL)) {
char *p;
rtp_resp_t rtp = { 0 };
short unsigned tport = 0, cport = 0;
uint8_t *buffer = NULL;
size_t size = 0;
// we are about to stream, do something if needed and optionally give buffers to play with
success = ctx->cmd_cb(RAOP_SETUP);
success = ctx->cmd_cb(RAOP_SETUP, &buffer, &size);
if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
@@ -605,7 +610,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
volume = (volume == -144.0) ? 0 : (1 + volume / 30);
success = ctx->cmd_cb(RAOP_VOLUME, volume);
} else if (body && (p = strcasestr(body, "progress")) != NULL) {
int start, current, stop = 0;
int start, current, stop = 0;
// we want ms, not s
@@ -672,9 +676,8 @@ void cleanup_rtsp(raop_ctx_t *ctx, bool abort) {
if (ctx->active_remote.running) {
#ifdef WIN32
pthread_join(ctx->active_remote.thread, NULL);
close_mDNS(ctx->active_remote.handle);
#else
#else
// need to make sure no search is on-going and reclaim task memory
// need to make sure no search is on-going and reclaim task memory
ctx->active_remote.running = false;
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);

View File

@@ -1,23 +1,19 @@
/*
* AirCast: Chromecast to AirPlay
*
* (c) Philippe 2016-2017, philippe_44@outlook.com
* (c) Philippe 2020, philippe_44@outlook.com
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
*/
#ifndef __RAOP_H
#define __RAOP_H
#pragma once
#include "platform.h"
#include "raop_sink.h"
struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency,
struct raop_ctx_s* raop_create(uint32_t host, char *name, unsigned char mac[6], int latency,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
void raop_delete(struct raop_ctx_s *ctx);
void raop_abort(struct raop_ctx_s *ctx);
bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
#endif

View File

@@ -161,7 +161,6 @@ static bool raop_sink_start(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
const char *hostname = NULL;
char sink_name[64-6] = CONFIG_AIRPLAY_NAME;
tcpip_adapter_ip_info_t ipInfo = { };
struct in_addr host;
tcpip_adapter_if_t ifs[] = { TCPIP_ADAPTER_IF_ETH, TCPIP_ADAPTER_IF_STA, TCPIP_ADAPTER_IF_AP };
// get various IP info
@@ -175,27 +174,25 @@ static bool raop_sink_start(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
LOG_INFO( "no hostname/IP found, can't start AirPlay");
return false;
}
host.s_addr = ipInfo.ip.addr;
// initialize mDNS
ESP_ERROR_CHECK( mdns_init() );
ESP_ERROR_CHECK( mdns_hostname_set(hostname) );
char * sink_name_buffer= (char *)config_alloc_get(NVS_TYPE_STR,"airplay_name");
if(sink_name_buffer != NULL){
if (sink_name_buffer != NULL){
memset(sink_name, 0x00, sizeof(sink_name));
strncpy(sink_name,sink_name_buffer,sizeof(sink_name)-1 );
free(sink_name_buffer);
}
LOG_INFO( "mdns hostname for ip %s set to: [%s] with servicename %s", inet_ntoa(host), hostname, sink_name);
LOG_INFO( "mdns hostname for ip %s set to: [%s] with servicename %s", inet_ntoa(ipInfo.ip.addr), hostname, sink_name);
// create RAOP instance, latency is set by controller
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
cmd_handler_chain = cmd_cb;
raop = raop_create(host, sink_name, mac, 0, cmd_handler, data_cb);
raop = raop_create(ipInfo.ip.addr, sink_name, mac, 0, cmd_handler, data_cb);
return true;
}
@@ -218,7 +215,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
raop_cbs.data = data_cb;
TimerHandle_t timer = xTimerCreate("raopStart", 5000 / portTICK_RATE_MS, pdTRUE, NULL, raop_start_handler);
xTimerStart(timer, portMAX_DELAY);
LOG_INFO( "delaying AirPlay start");
LOG_INFO( "Delaying AirPlay start");
}
}

View File

@@ -65,8 +65,8 @@
#define MS2TS(ms, rate) ((((u64_t) (ms)) * (rate)) / 1000)
#define TS2MS(ts, rate) NTP2MS(TS2NTP(ts,rate))
extern log_level raop_loglevel;
extern log_level raop_loglevel;
static log_level *loglevel = &raop_loglevel;
//#define __RTP_STORE
@@ -93,6 +93,7 @@ typedef struct audio_buffer_entry { // decoded audio packets
u32_t rtptime, last_resend;
s16_t *data;
int len;
bool allocated;
} abuf_t;
typedef struct rtp_s {
@@ -152,7 +153,7 @@ typedef struct rtp_s {
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
static void buffer_alloc(abuf_t *audio_buffer, int size, uint8_t *buf, size_t buf_size);
static void buffer_release(abuf_t *audio_buffer);
static void buffer_reset(abuf_t *audio_buffer);
static void buffer_push_packet(rtp_t *ctx);
@@ -208,6 +209,7 @@ static struct alac_codec_s* alac_init(int fmtp[32]) {
/*---------------------------------------------------------------------------*/
rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv, char *fmtpstr,
short unsigned pCtrlPort, short unsigned pTimingPort,
uint8_t *buffer, size_t size,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb)
{
int i = 0;
@@ -260,7 +262,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
ctx->alac_codec = alac_init(fmtp);
rc &= ctx->alac_codec != NULL;
rc &= ctx->alac_codec != NULL;
buffer_alloc(ctx->audio_buffer, ctx->frame_size*4, buffer, size);
// create rtp ports
for (i = 0; i < 3; i++) {
@@ -279,8 +281,9 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
pthread_create(&ctx->thread, NULL, rtp_thread_func, (void *) ctx);
#else
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
#else
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->thread = xTaskCreateStaticPinnedToCore( (TaskFunction_t) rtp_thread_func, "RTP_thread", RTP_STACK_SIZE, ctx,
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, ctx->xStack, ctx->xTaskBuffer,
CONFIG_PTHREAD_TASK_CORE_DEFAULT );
#endif
// cleanup everything if we failed
@@ -311,7 +314,7 @@ void rtp_end(rtp_t *ctx)
#else
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
SAFE_PTR_FREE(ctx->xTaskBuffer);
#endif
}
@@ -369,10 +372,18 @@ void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
}
/*---------------------------------------------------------------------------*/
static void buffer_alloc(abuf_t *audio_buffer, int size, uint8_t *buf, size_t buf_size) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
int i;
if (buf && buf_size >= size) {
audio_buffer[i].data = (s16_t*) buf;
audio_buffer[i].allocated = false;
buf += size;
buf_size -= size;
} else {
audio_buffer[i].allocated = true;
audio_buffer[i].data = malloc(size);
}
audio_buffer[i].ready = 0;
}
}
@@ -381,7 +392,7 @@ static void buffer_alloc(abuf_t *audio_buffer, int size) {
static void buffer_release(abuf_t *audio_buffer) {
int i;
for (i = 0; i < BUFFER_FRAMES; i++) {
int i;
if (audio_buffer[i].allocated) free(audio_buffer[i].data);
}
}

View File

@@ -12,6 +12,7 @@ typedef struct {
rtp_resp_t rtp_init(struct in_addr host, int latency,
char *aeskey, char *aesiv, char *fmtpstr,
short unsigned pCtrlPort, short unsigned pTimingPort,
uint8_t *buffer, size_t size,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
void rtp_end(struct rtp_s *ctx);
bool rtp_flush(struct rtp_s *ctx, unsigned short seqno, unsigned rtptime, bool exit_locked);

View File

@@ -18,6 +18,11 @@
#include "platform.h"
#include "pthread.h"
#ifndef WIN32
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#endif
#define NFREE(p) if (p) { free(p); p = NULL; }
typedef struct metadata_s {
@@ -46,9 +51,21 @@ char *strndup(const char *s, size_t n);
char *strndup(const char *s, size_t n);
int asprintf(char **strp, const char *fmt, ...);
void winsock_init(void);
void winsock_close(void);
void winsock_close(void);
#define SAFE_PTR_FREE(P) free(P)
#else
char *strlwr(char *str);
// reason is that TCB might be cleanup in idle task
#define SAFE_PTR_FREE(P) \
do { \
TimerHandle_t timer = xTimerCreate("cleanup", pdMS_TO_TICKS(10000), pdFALSE, P, _delayed_free); \
xTimerStart(timer, portMAX_DELAY); \
} while (0)
static void inline _delayed_free(TimerHandle_t xTimer) {
free(pvTimerGetTimerID(xTimer));
xTimerDelete(xTimer, portMAX_DELAY);
}
#endif
char* strextract(char *s1, char *beg, char *end);
@@ -67,5 +84,6 @@ char* kd_dump(key_data_t *kd);
char* http_send(int sock, char *method, key_data_t *rkd);
char* kd_lookup(key_data_t *kd, char *key);
bool kd_add(key_data_t *kd, char *key, char *value);
bool kd_add(key_data_t *kd, char *key, char *value);
char* kd_dump(key_data_t *kd);
void kd_free(key_data_t *kd);

View File

@@ -1,7 +1,7 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS . ${IDF_PATH}/components/driver
REQUIRES json tools platform_config display
PRIV_REQUIRES soc esp32 squeezelite
PRIV_REQUIRES soc esp32
)

View File

@@ -15,7 +15,6 @@
#include "accessors.h"
#include "globdefs.h"
#include "display.h"
#include "display.h"
#include "cJSON.h"
#include "driver/gpio.h"
#include "stdbool.h"
@@ -29,7 +28,6 @@
#include "driver/gpio.h"
#include "driver/spi_common_internal.h"
#include "esp32/rom/efuse.h"
#include "adac.h"
#include "trace.h"
#include "monitor.h"
#include "messaging.h"
@@ -109,16 +107,16 @@ bool is_spdif_config_locked(){
static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
char *p;
pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1;
if ((p = strcasestr(config, "bck")) != NULL) pin_config->bck_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "ws")) != NULL) pin_config->ws_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "do")) != NULL) pin_config->data_out_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "bck"))) sscanf(p, "bck%*[^=]=%d", &pin_config->bck_io_num);
if ((p = strcasestr(config, "ws"))) sscanf(p, "ws%*[^=]=%d", &pin_config->ws_io_num);
if ((p = strcasestr(config, "do"))) sscanf(p, "do%*[^=]=%d", &pin_config->data_out_num);
}
/****************************************************************************************
* Get i2s config structure from config string
*/
const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_pin = {
const i2s_platform_config_t * config_i2s_get_from_str(char * dac_config ){
static i2s_platform_config_t i2s_dac_pin = {
.i2c_addr = -1,
.sda= -1,
.scl = -1,
@@ -129,53 +127,26 @@ const i2s_platform_config_t * config_get_i2s_from_str(char * dac_config ){
strcpy(i2s_dac_pin.model, "i2s");
char * p=NULL;
if ((p = strcasestr(dac_config, "i2c")) != NULL) i2s_dac_pin.i2c_addr = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(dac_config, "sda")) != NULL) i2s_dac_pin.sda = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(dac_config, "scl")) != NULL) i2s_dac_pin.scl = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", i2s_dac_pin.model);
if ((p = strcasestr(dac_config, "mute")) != NULL) {
PARSE_PARAM(dac_config, "i2c", '=', i2s_dac_pin.i2c_addr);
PARSE_PARAM(dac_config, "sda", '=', i2s_dac_pin.sda);
PARSE_PARAM(dac_config, "scl", '=', i2s_dac_pin.scl);
PARSE_PARAM_STR(dac_config, "model", '=', i2s_dac_pin.model, 31);
if ((p = strcasestr(dac_config, "mute"))) {
char mute[8] = "";
sscanf(p, "%*[^=]=%7[^,]", mute);
i2s_dac_pin.mute_gpio = atoi(mute);
if ((p = strchr(mute, ':')) != NULL) i2s_dac_pin.mute_level = atoi(p + 1);
PARSE_PARAM(p, "mute", ':', i2s_dac_pin.mute_level);
}
return &i2s_dac_pin;
}
/****************************************************************************************
* Get eth config structure from config string
*/
const eth_config_t * config_get_eth_from_str(char * eth_config ){
static EXT_RAM_ATTR eth_config_t eth_pin = {
.rmii = false,
.model = "",
};
char * p=NULL;
if ((p = strcasestr(eth_config, "model")) != NULL) sscanf(p, "%*[^=]=%15[^,]", eth_pin.model);
if ((p = strcasestr(eth_config, "mdc")) != NULL) eth_pin.mdc = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "mdio")) != NULL) eth_pin.mdio = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "rst")) != NULL) eth_pin.rst = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "mosi")) != NULL) eth_pin.mosi = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "miso")) != NULL) eth_pin.miso = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "intr")) != NULL) eth_pin.intr = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "cs")) != NULL) eth_pin.cs = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "speed")) != NULL) eth_pin.speed = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "clk")) != NULL) eth_pin.clk = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(eth_config, "host")) != NULL) eth_pin.host = atoi(strchr(p, '=') + 1);
if (strcasestr(eth_pin.model, "lan8720")) eth_pin.rmii = true;
return &eth_pin;
}
/****************************************************************************************
* Get spdif config structure
*/
const i2s_platform_config_t * config_spdif_get( ){
char * spdif_config = config_spdif_get_string();
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_config;
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
memcpy(&i2s_dac_config, config_i2s_get_from_str(spdif_config), sizeof(i2s_dac_config));
free(spdif_config);
return &i2s_dac_config;
}
@@ -186,33 +157,11 @@ const i2s_platform_config_t * config_spdif_get( ){
const i2s_platform_config_t * config_dac_get(){
char * spdif_config = get_dac_config_string();
static EXT_RAM_ATTR i2s_platform_config_t i2s_dac_config;
memcpy(&i2s_dac_config, config_get_i2s_from_str(spdif_config), sizeof(i2s_dac_config));
memcpy(&i2s_dac_config, config_i2s_get_from_str(spdif_config), sizeof(i2s_dac_config));
free(spdif_config);
return &i2s_dac_config;
}
/****************************************************************************************
* Get ethernet config structure
*/
const eth_config_t * config_eth_get( ){
char * config = config_alloc_get_str("eth_config", CONFIG_ETH_CONFIG, "rst=" STR(CONFIG_ETH_PHY_RST_IO)
#if defined(CONFIG_ETH_LAN8720)
",model=lan8720"
#elif defined(CONFIG_ETH_DM9051)
",model=dm9051"
#endif
",mdc=" STR(CONFIG_ETH_MDC_IO) ",mdio=" STR(CONFIG_ETH_MDIO_IO)
",host=" STR(CONFIG_ETH_SPI_HOST) ",cs=" STR(CONFIG_ETH_SPI_CS_IO)
",mosi=" STR(CONFIG_ETH_SPI_MOSI_IO) ",miso=" STR(CONFIG_ETH_SPI_MISO_IO)
",intr=" STR(CONFIG_ETH_SPI_INTR_IO)
",clk=" STR(CONFIG_ETH_SPI_CLK_IO) ",speed=" STR(CONFIG_ETH_SPI_SPEED) );
static EXT_RAM_ATTR eth_config_t eth_config;
ESP_LOGD(TAG, "Ethernet config string %s", config);
memcpy(&eth_config, config_get_eth_from_str(config), sizeof(eth_config));
free(config);
return &eth_config;
}
/****************************************************************************************
*
*/
@@ -432,16 +381,18 @@ const display_config_t * config_display_get(){
sscanf(p, "%*[^:]:%u", &dstruct.depth);
dstruct.drivername = display_conf_get_driver_name(strchr(p, '=') + 1);
}
if ((p = strcasestr(config, "width")) != NULL) dstruct.width = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "height")) != NULL) dstruct.height = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "reset")) != NULL) dstruct.RST_pin = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "width", '=', dstruct.width);
PARSE_PARAM(config, "height", '=', dstruct.height);
PARSE_PARAM(config, "reset", '=', dstruct.RST_pin);
PARSE_PARAM(config, "address", '=', dstruct.address);
PARSE_PARAM(config, "cs", '=', dstruct.CS_pin);
PARSE_PARAM(config, "speed", '=', dstruct.speed);
PARSE_PARAM(config, "back", '=', dstruct.back);
if (strstr(config, "I2C") ) dstruct.type=i2c_name_type;
if ((p = strcasestr(config, "address")) != NULL) dstruct.address = atoi(strchr(p, '=') + 1);
if (strstr(config, "SPI") ) dstruct.type=spi_name_type;
if ((p = strcasestr(config, "cs")) != NULL) dstruct.CS_pin = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "speed")) != NULL) dstruct.speed = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "back")) != NULL) dstruct.back = atoi(strchr(p, '=') + 1);
dstruct.hflip= strcasestr(config, "HFlip") ? true : false;
dstruct.vflip= strcasestr(config, "VFlip") ? true : false;
dstruct.rotate= strcasestr(config, "rotate") ? true : false;
@@ -452,7 +403,7 @@ const display_config_t * config_display_get(){
*
*/
const i2c_config_t * config_i2c_get(int * i2c_port) {
char *nvs_item, *p;
char *nvs_item;
static i2c_config_t i2c = {
.mode = I2C_MODE_MASTER,
.sda_io_num = -1,
@@ -466,10 +417,10 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
nvs_item = config_alloc_get(NVS_TYPE_STR, "i2c_config");
if (nvs_item) {
if ((p = strcasestr(nvs_item, "scl")) != NULL) i2c.scl_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "sda")) != NULL) i2c.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "speed")) != NULL) i2c.master.clk_speed = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "port")) != NULL) i2c_system_port = atoi(strchr(p, '=') + 1);
PARSE_PARAM(nvs_item, "scl", '=', i2c.scl_io_num);
PARSE_PARAM(nvs_item, "sda", '=', i2c.sda_io_num);
PARSE_PARAM(nvs_item, "speed", '=', i2c.master.clk_speed);
PARSE_PARAM(nvs_item, "port", '=', i2c_system_port);
free(nvs_item);
}
if(i2c_port) {
@@ -482,6 +433,46 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
return &i2c;
}
/****************************************************************************************
* Get IO expander config structure from config string
*/
const gpio_exp_config_t* config_gpio_exp_get(int index) {
char *nvs_item, *item, *p;
static gpio_exp_config_t config;
// re-initialize config every time
memset(&config, 0, sizeof(config));
config.intr = -1; config.count = 16; config.base = GPIO_NUM_MAX; config.phy.port = i2c_system_port; config.phy.host = spi_system_host;
nvs_item = config_alloc_get(NVS_TYPE_STR, "gpio_exp_config");
if (!nvs_item || !*nvs_item) return NULL;
// search index items
for (item = strtok(nvs_item, ";"); index && item; index--) {
if ((item = strtok(NULL, ";")) == NULL) {
free(nvs_item);
return NULL;
}
}
PARSE_PARAM(item, "addr", '=', config.phy.addr);
PARSE_PARAM(item, "cs", '=', config.phy.cs_pin);
PARSE_PARAM(item, "speed", '=', config.phy.speed);
PARSE_PARAM(item, "intr", '=', config.intr);
PARSE_PARAM(item, "base", '=', config.base);
PARSE_PARAM(item, "count", '=', config.count);
PARSE_PARAM_STR(item, "model", '=', config.model, 31);
if ((p = strcasestr(item, "port")) != NULL) {
char port[8] = "";
sscanf(p, "%*[^=]=%7[^,]", port);
if (strcasestr(port, "dac")) config.phy.port = 0;
}
free(nvs_item);
return &config;
}
/****************************************************************************************
*
*/
@@ -560,8 +551,8 @@ const set_GPIO_struct_t * get_gpio_struct(){
*
*/
const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
char *nvs_item, *p;
static EXT_RAM_ATTR spi_bus_config_t spi = {
char *nvs_item;
static spi_bus_config_t spi = {
.mosi_io_num = -1,
.sclk_io_num = -1,
.miso_io_num = -1,
@@ -571,12 +562,13 @@ const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
nvs_item = config_alloc_get_str("spi_config", CONFIG_SPI_CONFIG, NULL);
if (nvs_item) {
if ((p = strcasestr(nvs_item, "data")) != NULL) spi.mosi_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "mosi")) != NULL) spi.mosi_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "miso")) != NULL) spi.miso_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "clk")) != NULL) spi.sclk_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "dc")) != NULL) spi_system_dc_gpio = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "host")) != NULL) spi_system_host = atoi(strchr(p, '=') + 1);
PARSE_PARAM(nvs_item, "data", '=', spi.mosi_io_num);
PARSE_PARAM(nvs_item, "mosi", '=', spi.mosi_io_num);
PARSE_PARAM(nvs_item, "miso", '=', spi.miso_io_num);
PARSE_PARAM(nvs_item, "clk", '=', spi.sclk_io_num);
PARSE_PARAM(nvs_item, "dc", '=', spi_system_dc_gpio);
// only VSPI (1) can be used as Flash and PSRAM run at 80MHz
// if ((p = strcasestr(nvs_item, "host")) != NULL) spi_system_host = atoi(strchr(p, '=') + 1);
free(nvs_item);
}
if(spi_host) *spi_host = spi_system_host;
@@ -611,11 +603,11 @@ const rotary_struct_t * config_rotary_get() {
char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
if (config && *config) {
char *p;
// parse config
if ((p = strcasestr(config, "A")) != NULL) rotary.A = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "B")) != NULL) rotary.B = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "SW")) != NULL) rotary.SW = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "A", '=', rotary.A);
PARSE_PARAM(config, "B", '=', rotary.B);
PARSE_PARAM(config, "SW", '=', rotary.SW);
if ((p = strcasestr(config, "knobonly")) != NULL) {
p = strchr(p, '=');
rotary.knobonly = true;
@@ -660,13 +652,10 @@ cJSON * add_gpio_for_value(cJSON * list,const char * name,int gpio, const char *
*/
cJSON * add_gpio_for_name(cJSON * list,const char * nvs_entry,const char * name, const char * prefix, bool fixed){
cJSON * llist = list?list:cJSON_CreateArray();
char *p;
int gpioNum=0;
if ((p = strcasestr(nvs_entry, name)) != NULL) {
gpioNum = atoi(strchr(p, '=') + 1);
if(gpioNum>=0){
cJSON_AddItemToArray(llist,get_gpio_entry(name,prefix,gpioNum,fixed));
}
PARSE_PARAM(nvs_entry, name, '=', gpioNum);
if(gpioNum>=0){
cJSON_AddItemToArray(llist,get_gpio_entry(name,prefix,gpioNum,fixed));
}
return llist;
}
@@ -988,14 +977,11 @@ cJSON * get_gpio_list(bool refresh) {
#ifndef CONFIG_BAT_LOCKED
char *bat_config = config_alloc_get_default(NVS_TYPE_STR, "bat_config", NULL, 0);
if (bat_config) {
char *p;
int channel;
if ((p = strcasestr(bat_config, "channel") ) != NULL) {
channel = atoi(strchr(p, '=') + 1);
if(channel != -1){
if(adc1_pad_get_io_num(channel,&gpio_num )==ESP_OK){
cJSON_AddItemToArray(gpio_list,get_gpio_entry("bat","other",gpio_num,false));
}
int channel = -1;
PARSE_PARAM(bat_config, "channem", '=', channel);
if(channel != -1){
if(adc1_pad_get_io_num(channel,&gpio_num )==ESP_OK){
cJSON_AddItemToArray(gpio_list,get_gpio_entry("bat","other",gpio_num,false));
}
}
free(bat_config);

View File

@@ -12,8 +12,11 @@
#include "driver/i2c.h"
#include "driver/i2s.h"
#include "driver/spi_master.h"
#include "gpio_exp.h"
extern const char *i2c_name_type;
extern const char *spi_name_type;
typedef struct {
int width;
int height;
@@ -85,13 +88,13 @@ typedef struct {
} gpio_entry_t;
const display_config_t * config_display_get();
const eth_config_t * config_eth_get( );
esp_err_t config_display_set(const display_config_t * config);
esp_err_t config_i2c_set(const i2c_config_t * config, int port);
esp_err_t config_i2s_set(const i2s_platform_config_t * config, const char * nvs_name);
esp_err_t config_spi_set(const spi_bus_config_t * config, int host, int dc);
const i2c_config_t * config_i2c_get(int * i2c_port);
const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host);
const gpio_exp_config_t * config_gpio_exp_get(int index);
void parse_set_GPIO(void (*cb)(int gpio, char *value));
const i2s_platform_config_t * config_dac_get();
const i2s_platform_config_t * config_spdif_get( );

View File

@@ -67,12 +67,12 @@ static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(
static const char * TAG = "audio controls";
static actrls_config_t *json_config;
cJSON * control_profiles = NULL;
static actrls_t default_controls, current_controls;
static EXT_RAM_ATTR actrls_t default_controls, current_controls;
static actrls_hook_t *default_hook, *current_hook;
static bool default_raw_controls, current_raw_controls;
static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
static struct {
static EXT_RAM_ATTR struct {
bool long_state;
bool volume_lock;
TimerHandle_t timer;
@@ -137,10 +137,10 @@ esp_err_t actrls_init(const char *profile_name) {
int A = -1, B = -1, SW = -1, longpress = 0;
// parse config
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "knobonly")) != NULL) {
PARSE_PARAM(config, "A", '=', A);
PARSE_PARAM(config, "B", '=', B);
PARSE_PARAM(config, "SW", '=', SW);
if ((p = strcasestr(config, "knobonly"))) {
p = strchr(p, '=');
int double_press = p ? atoi(p + 1) : 350;
rotary.timer = xTimerCreate("knobTimer", double_press / portTICK_RATE_MS, pdFALSE, NULL, rotary_timer);

View File

@@ -79,13 +79,12 @@ void battery_svc_init(void) {
char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
if (nvs_item) {
char *p;
#ifndef CONFIG_BAT_LOCKED
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "atten")) != NULL) battery.attenuation = atoi(strchr(p, '=') + 1);
PARSE_PARAM(nvs_item, "channel", '=', battery.channel);
PARSE_PARAM(nvs_item, "scale", '=', battery.scale);
PARSE_PARAM(nvs_item, "atten", '=', battery.attenuation);
#endif
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
PARSE_PARAM(nvs_item, "cells", '=', battery.cells);
free(nvs_item);
}

View File

@@ -21,16 +21,17 @@
#include "esp_task.h"
#include "driver/gpio.h"
#include "driver/rmt.h"
#include "gpio_exp.h"
#include "buttons.h"
#include "rotary_encoder.h"
#include "globdefs.h"
static const char * TAG = "buttons";
static int n_buttons = 0;
static EXT_RAM_ATTR int n_buttons;
#define BUTTON_STACK_SIZE 4096
#define MAX_BUTTONS 16
#define MAX_BUTTONS 32
#define DEBOUNCE 50
#define BUTTON_QUEUE_LEN 10
@@ -47,7 +48,8 @@ static EXT_RAM_ATTR struct button_s {
TimerHandle_t timer;
} buttons[MAX_BUTTONS];
static EXT_RAM_ATTR struct {
// can't use EXT_RAM_ATTR for initialized structure
static struct {
int gpio, level;
struct button_s *button;
} polled_gpio[] = { {36, -1, NULL}, {39, -1, NULL}, {-1, -1, NULL} };
@@ -67,10 +69,11 @@ static EXT_RAM_ATTR struct {
infrared_handler handler;
} infrared;
static xQueueHandle button_evt_queue;
static QueueSetHandle_t common_queue_set;
static EXT_RAM_ATTR QueueHandle_t button_queue;
static EXT_RAM_ATTR QueueSetHandle_t common_queue_set;
static void buttons_task(void* arg);
static void buttons_handler(struct button_s *button, int level);
/****************************************************************************************
* Start task needed by button,s rotaty and infrared
@@ -81,45 +84,38 @@ static void common_task_init(void) {
if (!common_queue_set) {
common_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 2, xStack, &xTaskBuffer);
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 2, xStack, &xTaskBuffer);
}
}
/****************************************************************************************
* GPIO low-level handler
* GPIO low-level ISR handler
*/
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
struct button_s *button = (struct button_s*) arg;
BaseType_t woken = pdFALSE;
if (xTimerGetPeriod(button->timer) > button->debounce / portTICK_RATE_MS) xTimerChangePeriodFromISR(button->timer, button->debounce / portTICK_RATE_MS, &woken); // does that restart the timer?
else xTimerResetFromISR(button->timer, &woken);
if (xTimerGetPeriod(button->timer) > pdMS_TO_TICKS(button->debounce)) {
if (button->gpio < GPIO_NUM_MAX) xTimerChangePeriodFromISR(button->timer, pdMS_TO_TICKS(button->debounce), &woken);
else xTimerChangePeriod(button->timer, pdMS_TO_TICKS(button->debounce), pdMS_TO_TICKS(10));
} else {
if (button->gpio < GPIO_NUM_MAX) xTimerResetFromISR(button->timer, &woken);
else xTimerReset(button->timer, portMAX_DELAY);
}
if (woken) portYIELD_FROM_ISR();
ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level);
}
/****************************************************************************************
* Buttons debounce/longpress timer
*/
static void buttons_timer( TimerHandle_t xTimer ) {
static void buttons_timer_handler( TimerHandle_t xTimer ) {
struct button_s *button = (struct button_s*) pvTimerGetTimerID (xTimer);
button->level = gpio_get_level(button->gpio);
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
if (button->long_press && !button->long_timer && button->level == button->type) {
// detect a long press, so hold event generation
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
xTimerChangePeriod(xTimer, button->long_press / portTICK_RATE_MS, 0);
button->long_timer = true;
} else {
// send a button pressed/released event (content is copied in queue)
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
// queue will have a copy of button's context
xQueueSend(button_evt_queue, button, 0);
button->long_timer = false;
}
// if this is an expanded GPIO, must give cache a chance
buttons_handler(button, gpio_exp_get_level(button->gpio, (button->debounce * 3) / 2, NULL));
}
/****************************************************************************************
@@ -133,11 +129,33 @@ static void buttons_polling( TimerHandle_t xTimer ) {
if (level != polled_gpio[i].level) {
polled_gpio[i].level = level;
buttons_timer(polled_gpio[i].button->timer);
buttons_handler(polled_gpio[i].button, level);
}
}
}
/****************************************************************************************
* Buttons timer handler for press/longpress
*/
static void buttons_handler(struct button_s *button, int level) {
button->level = level;
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
if (button->long_press && !button->long_timer && button->level == button->type) {
// detect a long press, so hold event generation
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
xTimerChangePeriod(button->timer, button->long_press / portTICK_RATE_MS, 0);
button->long_timer = true;
} else {
// send a button pressed/released event (content is copied in queue)
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
// queue will have a copy of button's context
xQueueSend(button_queue, button, 0);
button->long_timer = false;
}
}
/****************************************************************************************
* Tasks that calls the appropriate functions when buttons are pressed
*/
@@ -150,13 +168,13 @@ static void buttons_task(void* arg) {
// wait on button, rotary and infrared queues
if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
if (xActivatedMember == button_evt_queue) {
if (xActivatedMember == button_queue) {
struct button_s button;
button_event_e event;
button_press_e press;
// received a button event
xQueueReceive(button_evt_queue, &button, 0);
xQueueReceive(button_queue, &button, 0);
event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
@@ -175,18 +193,18 @@ static void buttons_task(void* arg) {
if (event == BUTTON_RELEASED) {
// early release of a long-press button, send press/release
if (!button.shifting) {
(*button.handler)(button.client, BUTTON_PRESSED, press, false);
(*button.handler)(button.client, BUTTON_RELEASED, press, false);
button.handler(button.client, BUTTON_PRESSED, press, false);
button.handler(button.client, BUTTON_RELEASED, press, false);
}
// button is a copy, so need to go to real context
button.self->shifting = false;
} else if (!button.shifting) {
// normal long press and not shifting so don't discard
(*button.handler)(button.client, BUTTON_PRESSED, press, true);
button.handler(button.client, BUTTON_PRESSED, press, true);
}
} else {
// normal press/release of a button or release of a long-press button
if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
if (!button.shifting) button.handler(button.client, event, press, button.long_press);
// button is a copy, so need to go to real context
button.self->shifting = false;
}
@@ -195,12 +213,12 @@ static void buttons_task(void* arg) {
// received a rotary event
xQueueReceive(rotary.queue, &event, 0);
ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false);
ROTARY_RIGHT : ROTARY_LEFT, false);
} else {
// this is IR
infrared_receive(infrared.rb, infrared.handler);
@@ -224,9 +242,9 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
if (!n_buttons) {
button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
common_task_init();
xQueueAddToSet( button_evt_queue, common_queue_set );
xQueueAddToSet( button_queue, common_queue_set );
}
// just in case this structure is allocated in a future release
@@ -240,7 +258,7 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
buttons[n_buttons].long_press = long_press;
buttons[n_buttons].shifter_gpio = shifter_gpio;
buttons[n_buttons].type = type;
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer);
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer_handler);
buttons[n_buttons].self = buttons + n_buttons;
for (int i = 0; i < n_buttons; i++) {
@@ -257,24 +275,21 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
}
}
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_INPUT);
// we need any edge detection
gpio_set_intr_type(gpio, GPIO_INTR_ANYEDGE);
gpio_pad_select_gpio_x(gpio);
gpio_set_direction_x(gpio, GPIO_MODE_INPUT);
// do we need pullup or pulldown
if (pull) {
if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) {
if (type == BUTTON_LOW) gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY);
else gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY);
if (GPIO_IS_VALID_OUTPUT_GPIO(gpio) || gpio >= GPIO_NUM_MAX) {
if (type == BUTTON_LOW) gpio_set_pull_mode_x(gpio, GPIO_PULLUP_ONLY);
else gpio_set_pull_mode_x(gpio, GPIO_PULLDOWN_ONLY);
} else {
ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
}
}
// and initialize level ...
buttons[n_buttons].level = gpio_get_level(gpio);
buttons[n_buttons].level = gpio_get_level_x(gpio);
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
for (int i = 0; polled_gpio[i].gpio != -1; i++) if (polled_gpio[i].gpio == gpio) {
@@ -282,19 +297,21 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
polled_timer = xTimerCreate("buttonsPolling", 100 / portTICK_RATE_MS, pdTRUE, polled_gpio, buttons_polling);
xTimerStart(polled_timer, portMAX_DELAY);
}
polled_gpio[i].button = buttons + n_buttons;
polled_gpio[i].level = gpio_get_level(gpio);
ESP_LOGW(TAG, "creating polled gpio %u, level %u", gpio, polled_gpio[i].level);
gpio = -1;
break;
}
// only create timers and ISR is this is not a polled gpio
// only create ISR if this is not a polled gpio
if (gpio != -1) {
gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
gpio_intr_enable(gpio);
// we need any edge detection
gpio_set_intr_type_x(gpio, GPIO_INTR_ANYEDGE);
gpio_isr_handler_add_x(gpio, gpio_isr_handler, buttons + n_buttons);
gpio_intr_enable_x(gpio);
}
n_buttons++;
@@ -362,7 +379,7 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
}
/****************************************************************************************
* Create rotary encoder
* Rotary encoder handler
*/
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Rotary push-button %d", event);

View File

@@ -21,11 +21,3 @@ typedef struct {
int timer, base_channel, max;
} pwm_system_t;
extern pwm_system_t pwm_system;
#ifdef CONFIG_SQUEEZEAMP
#define ADAC dac_tas57xx
#elif defined(CONFIG_A1S)
#define ADAC dac_a1s
#else
#define ADAC dac_external
#endif

View File

@@ -0,0 +1,737 @@
/* GDS Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include "esp_task.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "gpio_exp.h"
#define GPIO_EXP_INTR 0x100
#define GPIO_EXP_WRITE 0x200
/*
shadow register is both output and input, so we assume that reading to the
ports also reads the value set on output
*/
typedef struct gpio_exp_s {
uint32_t first, last;
int intr;
bool intr_pending;
struct {
struct gpio_exp_phy_s phy;
spi_device_handle_t spi_handle;
};
uint32_t shadow, pending;
TickType_t age;
SemaphoreHandle_t mutex;
uint32_t r_mask, w_mask;
uint32_t pullup, pulldown;
struct gpio_exp_isr_s {
gpio_isr_t handler;
void *arg;
TimerHandle_t timer;
} isr[32];
struct gpio_exp_model_s const *model;
} gpio_exp_t;
typedef struct {
enum { ASYNC_WRITE } type;
int gpio;
int level;
gpio_exp_t *expander;
} queue_request_t;
static const char TAG[] = "gpio expander";
static void IRAM_ATTR intr_isr_handler(void* arg);
static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio);
static void pca9535_set_direction(gpio_exp_t* self);
static uint32_t pca9535_read(gpio_exp_t* self);
static void pca9535_write(gpio_exp_t* self);
static uint32_t pca85xx_read(gpio_exp_t* self);
static void pca85xx_write(gpio_exp_t* self);
static esp_err_t mcp23017_init(gpio_exp_t* self);
static void mcp23017_set_pull_mode(gpio_exp_t* self);
static void mcp23017_set_direction(gpio_exp_t* self);
static uint32_t mcp23017_read(gpio_exp_t* self);
static void mcp23017_write(gpio_exp_t* self);
static esp_err_t mcp23s17_init(gpio_exp_t* self);
static void mcp23s17_set_pull_mode(gpio_exp_t* self);
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 service_handler(void *arg);
static void debounce_handler( TimerHandle_t xTimer );
static esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len);
static uint32_t i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len);
static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy);
static esp_err_t spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len);
static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len);
static const struct gpio_exp_model_s {
char *model;
gpio_int_type_t trigger;
esp_err_t (*init)(gpio_exp_t* self);
uint32_t (*read)(gpio_exp_t* self);
void (*write)(gpio_exp_t* self);
void (*set_direction)(gpio_exp_t* self);
void (*set_pull_mode)(gpio_exp_t* self);
} registered[] = {
{ .model = "pca9535",
.trigger = GPIO_INTR_LOW_LEVEL,
.set_direction = pca9535_set_direction,
.read = pca9535_read,
.write = pca9535_write, },
{ .model = "pca85xx",
.trigger = GPIO_INTR_LOW_LEVEL,
.read = pca85xx_read,
.write = pca85xx_write, },
{ .model = "mcp23017",
.trigger = GPIO_INTR_LOW_LEVEL,
.init = mcp23017_init,
.set_direction = mcp23017_set_direction,
.set_pull_mode = mcp23017_set_pull_mode,
.read = mcp23017_read,
.write = mcp23017_write, },
{ .model = "mcp23s17",
.trigger = GPIO_INTR_LOW_LEVEL,
.init = mcp23s17_init,
.set_direction = mcp23s17_set_direction,
.set_pull_mode = mcp23s17_set_pull_mode,
.read = mcp23s17_read,
.write = mcp23s17_write, },
};
static EXT_RAM_ATTR uint8_t n_expanders;
static EXT_RAM_ATTR QueueHandle_t message_queue;
static EXT_RAM_ATTR gpio_exp_t expanders[4];
static EXT_RAM_ATTR TaskHandle_t service_task;
/******************************************************************************
* Retrieve base from an expander reference
*/
uint32_t gpio_exp_get_base(gpio_exp_t *expander) {
return expander->first;
}
/******************************************************************************
* Retrieve reference from a GPIO
*/
gpio_exp_t *gpio_exp_get_expander(int gpio) {
int _gpio = gpio;
return find_expander(NULL, &_gpio);
}
/******************************************************************************
* Create an I2C expander
*/
gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) {
gpio_exp_t *expander = expanders + n_expanders;
if (config->base < GPIO_NUM_MAX || n_expanders == sizeof(expanders)/sizeof(gpio_exp_t)) {
ESP_LOGE(TAG, "Base %d GPIO must be at least %d for %s or too many expanders %d", config->base, GPIO_NUM_MAX, config->model, n_expanders);
return NULL;
}
// See if we know that model (expanders is zero-initialized)
for (int i = 0; !expander->model && i < sizeof(registered)/sizeof(struct gpio_exp_model_s); i++) {
if (strcasestr(config->model, registered[i].model)) expander->model = registered + i;
}
// well... try again
if (!expander->model) {
ESP_LOGE(TAG, "Unknown GPIO expansion chip %s", config->model);
return NULL;
}
memcpy(&expander->phy, &config->phy, sizeof(struct gpio_exp_phy_s));
// try to initialize the expander if required
if (expander->model->init && expander->model->init(expander) != ESP_OK) {
ESP_LOGE(TAG, "Cannot create GPIO expander %s, check i2c/spi configuration", config->model);
return NULL;
}
n_expanders++;
expander->first = config->base;
expander->last = config->base + config->count - 1;
expander->intr = config->intr;
expander->mutex = xSemaphoreCreateMutex();
// create a task to handle asynchronous requests (only write at this time)
if (!message_queue) {
// we allocate TCB but stack is static to avoid SPIRAM fragmentation
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
static EXT_RAM_ATTR StackType_t xStack[4*1024] __attribute__ ((aligned (4)));
message_queue = xQueueCreate(4, sizeof(queue_request_t));
service_task = xTaskCreateStatic(service_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
}
// set interrupt if possible
if (config->intr >= 0) {
gpio_pad_select_gpio(config->intr);
gpio_set_direction(config->intr, GPIO_MODE_INPUT);
switch (expander->model->trigger) {
case GPIO_INTR_NEGEDGE:
case GPIO_INTR_LOW_LEVEL:
gpio_set_pull_mode(config->intr, GPIO_PULLUP_ONLY);
break;
case GPIO_INTR_POSEDGE:
case GPIO_INTR_HIGH_LEVEL:
gpio_set_pull_mode(config->intr, GPIO_PULLDOWN_ONLY);
break;
default:
gpio_set_pull_mode(config->intr, GPIO_PULLUP_PULLDOWN);
break;
}
gpio_set_intr_type(config->intr, expander->model->trigger);
gpio_isr_handler_add(config->intr, intr_isr_handler, expander);
gpio_intr_enable(config->intr);
}
ESP_LOGI(TAG, "Create GPIO expander %s at base %u with intr %d at @%x on port/host %d/%d", config->model, config->base, config->intr, config->phy.addr, config->phy.port, config->phy.host);
return expander;
}
/******************************************************************************
* Add ISR handler for a GPIO
*/
esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr_handler, uint32_t debounce, void *arg, struct gpio_exp_s *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_add(gpio, isr_handler, arg);
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
expander->isr[gpio].handler = isr_handler;
expander->isr[gpio].arg = arg;
if (debounce) expander->isr[gpio].timer = xTimerCreate("gpioExpDebounce", pdMS_TO_TICKS(debounce),
pdFALSE, expander->isr + gpio, debounce_handler );
return ESP_OK;
}
/******************************************************************************
* Remove ISR handler for a GPIO
*/
esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_remove(gpio);
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
if (expander->isr[gpio].timer) xTimerDelete(expander->isr[gpio].timer, portMAX_DELAY);
memset(expander->isr + gpio, 0, sizeof(struct gpio_exp_isr_s));
return ESP_OK;
}
/******************************************************************************
* Set GPIO direction
*/
esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_direction(gpio, mode);
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
if (mode == GPIO_MODE_INPUT) {
expander->r_mask |= 1 << gpio;
expander->shadow = expander->model->read(expander);
expander->age = ~xTaskGetTickCount();
} else {
expander->w_mask |= 1 << gpio;
}
if (expander->r_mask & expander->w_mask) {
xSemaphoreGive(expander->mutex);
ESP_LOGE(TAG, "GPIO %d on expander base %u can't be r/w", gpio, expander->first);
return ESP_ERR_INVALID_ARG;
}
// most expanders want unconfigured GPIO to be set to output
if (expander->model->set_direction) expander->model->set_direction(expander);
xSemaphoreGive(expander->mutex);
return ESP_OK;
}
/******************************************************************************
* Get GPIO level with cache
*/
int gpio_exp_get_level(int gpio, int age, gpio_exp_t *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_get_level(gpio);
if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
uint32_t now = xTaskGetTickCount();
// return last thing we had if we can't get the mutex
if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) {
ESP_LOGW(TAG, "Can't get mutex for GPIO %d", expander->first + gpio);
return (expander->shadow >> gpio) & 0x01;
}
// re-read the expander if data is too old
if (age >= 0 && now - expander->age >= pdMS_TO_TICKS(age)) {
uint32_t value = expander->model->read(expander);
expander->pending |= (expander->shadow ^ value) & expander->r_mask;
expander->shadow = value;
expander->age = now;
}
// clear pending bit
expander->pending &= ~(1 << gpio);
xSemaphoreGive(expander->mutex);
ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
return (expander->shadow >> gpio) & 0x01;
}
/******************************************************************************
* Set GPIO level with cache
*/
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_level(gpio, level);
if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
uint32_t mask = 1 << gpio;
// very limited risk with lack of semaphore here
if ((expander->w_mask & mask) == 0) {
ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
return ESP_ERR_INVALID_ARG;
}
if (direct) {
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
level = level ? mask : 0;
mask &= expander->shadow;
// only write if shadow not up to date
if ((mask ^ level) && expander->model->write) {
expander->shadow = (expander->shadow & ~(mask | level)) | level;
expander->model->write(expander);
}
xSemaphoreGive(expander->mutex);
ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
} else {
queue_request_t request = { .gpio = gpio, .level = level, .type = ASYNC_WRITE, .expander = expander };
if (xQueueSend(message_queue, &request, 0) == pdFALSE) return ESP_ERR_INVALID_RESPONSE;
// notify service task that will write it when it can
xTaskNotify(service_task, GPIO_EXP_WRITE, eSetValueWithoutOverwrite);
}
return ESP_OK;
}
/******************************************************************************
* Set GPIO pullmode
*/
esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *expander) {
if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_pull_mode(gpio, mode);
if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
expander->pullup &= ~(1 << gpio);
expander->pulldown &= ~(1 << gpio);
if (mode == GPIO_PULLUP_ONLY || mode == GPIO_PULLUP_PULLDOWN) expander->pullup |= 1 << gpio;
if (mode == GPIO_PULLDOWN_ONLY || mode == GPIO_PULLUP_PULLDOWN) expander->pulldown |= 1 << gpio;
expander->model->set_pull_mode(expander);
return ESP_OK;
}
return ESP_ERR_INVALID_ARG;
}
/******************************************************************************
* Wrapper function
*/
esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode) {
if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode);
return gpio_exp_set_pull_mode(gpio, mode, NULL);
}
esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode) {
if (gpio < GPIO_NUM_MAX) return gpio_set_direction(gpio, mode);
return gpio_exp_set_direction(gpio, mode, NULL);
}
int gpio_get_level_x(int gpio) {
if (gpio < GPIO_NUM_MAX) return gpio_get_level(gpio);
return gpio_exp_get_level(gpio, 10, NULL);
}
esp_err_t gpio_set_level_x(int gpio, int level) {
if (gpio < GPIO_NUM_MAX) return gpio_set_level(gpio, level);
return gpio_exp_set_level(gpio, level, false, NULL);
}
esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args) {
if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_add(gpio, isr_handler, args);
return gpio_exp_isr_handler_add(gpio, isr_handler, 0, args, NULL);
}
esp_err_t gpio_isr_handler_remove_x(int gpio) {
if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_remove(gpio);
return gpio_exp_isr_handler_remove(gpio, NULL);
}
/****************************************************************************************
* INTR low-level handler
*/
static void IRAM_ATTR intr_isr_handler(void* arg) {
gpio_exp_t *self = (gpio_exp_t*) arg;
BaseType_t woken = pdFALSE;
// edge interrupts do not work because of read/clear = potential short pulse
gpio_intr_disable(self->intr);
// activate all, including ourselves
for (int i = 0; i < n_expanders; i++) if (expanders[i].intr == self->intr) expanders[i].intr_pending = true;
xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken);
if (woken) portYIELD_FROM_ISR();
ESP_EARLY_LOGD(TAG, "INTR for expander base %d", gpio_exp_get_base(self));
}
/****************************************************************************************
* INTR debounce handler
*/
static void debounce_handler( TimerHandle_t xTimer ) {
struct gpio_exp_isr_s *isr = (struct gpio_exp_isr_s*) pvTimerGetTimerID (xTimer);
isr->handler(isr->arg);
}
/****************************************************************************************
* Service task
*/
void service_handler(void *arg) {
while (1) {
queue_request_t request;
uint32_t notif = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// we have been notified of an interrupt
if (notif == GPIO_EXP_INTR) {
/* If we want a smarter bitmap of expanders with a pending interrupt
we'll have to disable interrupts while clearing that bitmap. For
now, a loop will do */
for (int i = 0; i < n_expanders; i++) {
gpio_exp_t *expander = expanders + i;
// no interrupt for that gpio or not pending (safe as interrupt is disabled)
if (expander->intr < 0 || !expander->intr_pending) continue;
xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50));
// read GPIOs and clear all pending status
uint32_t value = expander->model->read(expander);
expander->age = xTaskGetTickCount();
// re-enable interrupt now that it has been cleared
expander->intr_pending = false;
gpio_intr_enable(expander->intr);
uint32_t pending = expander->pending | ((expander->shadow ^ value) & expander->r_mask);
expander->shadow = value;
expander->pending = 0;
xSemaphoreGive(expander->mutex);
ESP_LOGD(TAG, "Handling GPIO %d reads 0x%04x and has 0x%04x pending", expander->first, expander->shadow, pending);
for (int gpio = 31, clz = 0; pending && clz < 31; pending <<= (clz + 1)) {
clz = __builtin_clz(pending);
gpio -= clz;
if (expander->isr[gpio].timer) xTimerReset(expander->isr[gpio].timer, 1); // todo 0
else if (expander->isr[gpio].handler) expander->isr[gpio].handler(expander->isr[gpio].arg);
}
}
}
// check if we have some other pending requests
while (xQueueReceive(message_queue, &request, 0) == pdTRUE) {
esp_err_t err = gpio_exp_set_level(request.gpio, request.level, true, request.expander);
if (err != ESP_OK) ESP_LOGW(TAG, "Can't execute async GPIO %d write request (%d)", request.gpio, err);
}
}
}
/****************************************************************************************
* Find the expander related to base
*/
static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
// a mutex would be better, but risk is so small...
for (int i = 0; !expander && i < n_expanders; i++) {
if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i;
}
// normalize GPIO number
if (expander && *gpio >= expander->first) *gpio -= expander->first;
return expander;
}
/****************************************************************************************
DRIVERS
****************************************************************************************/
/****************************************************************************************
* PCA9535 family : direction, read and write
*/
static void pca9535_set_direction(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x06, self->r_mask, 2);
}
static uint32_t pca9535_read(gpio_exp_t* self) {
return i2c_read(self->phy.port, self->phy.addr, 0x00, 2);
}
static void pca9535_write(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x02, self->shadow, 2);
}
/****************************************************************************************
* PCA85xx family : read and write
*/
static uint32_t pca85xx_read(gpio_exp_t* self) {
// must return the full set of pins, not just inputs
uint32_t data = i2c_read(self->phy.port, self->phy.addr, 0xff, 2);
return (data & self->r_mask) | (self->shadow & ~self->r_mask);
}
static void pca85xx_write(gpio_exp_t* self) {
/*
There is no good option with this chip: normally, unused pin should be set to input
to avoid any conflict but then they float and create tons of suprious. So option 1 is
to le tthem float and option 2 is to set them as output to 0.
In addition, setting an output pin to 1 equals is making it an input and if this is
use to short a led (e.g.) instead of being the sink, the it generates a spurious
*/
// option 1
// i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | ~self->w_mask, 2);
// option 2
i2c_write(self->phy.port, self->phy.addr, 0xff, (self->shadow & self->w_mask) | self->r_mask, 2);
}
/****************************************************************************************
* MCP23017 family : init, direction, read and write
*/
static esp_err_t mcp23017_init(gpio_exp_t* self) {
/*
0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low
not sure about this funny change of mapping of the control register itself, really?
*/
esp_err_t err = i2c_write(self->phy.port, self->phy.addr, 0x05, 0x74, 1);
err |= i2c_write(self->phy.port, self->phy.addr, 0x0a, 0x74, 1);
// no interrupt on comparison or on change
err |= i2c_write(self->phy.port, self->phy.addr, 0x04, 0x00, 2);
err |= i2c_write(self->phy.port, self->phy.addr, 0x08, 0x00, 2);
return err;
}
static void mcp23017_set_direction(gpio_exp_t* self) {
// default to input and set real input to generate interrupt
i2c_write(self->phy.port, self->phy.addr, 0x00, ~self->w_mask, 2);
i2c_write(self->phy.port, self->phy.addr, 0x04, self->r_mask, 2);
}
static void mcp23017_set_pull_mode(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x0c, self->pullup, 2);
}
static uint32_t mcp23017_read(gpio_exp_t* self) {
// read the pins value, not the stored one @interrupt
return i2c_read(self->phy.port, self->phy.addr, 0x12, 2);
}
static void mcp23017_write(gpio_exp_t* self) {
i2c_write(self->phy.port, self->phy.addr, 0x12, self->shadow, 2);
}
/****************************************************************************************
* MCP23s17 family : init, direction, read and write
*/
static esp_err_t mcp23s17_init(gpio_exp_t* self) {
if ((self->spi_handle = spi_config(&self->phy)) == NULL) return ESP_ERR_INVALID_ARG;
/*
0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low
not sure about this funny change of mapping of the control register itself, really?
*/
esp_err_t err = spi_write(self->spi_handle, self->phy.addr, 0x05, 0x74, 1);
err |= spi_write(self->spi_handle, self->phy.addr, 0x0a, 0x74, 1);
// no interrupt on comparison or on change
err |= spi_write(self->spi_handle, self->phy.addr, 0x04, 0x00, 2);
err |= spi_write(self->spi_handle, self->phy.addr, 0x08, 0x00, 2);
return err;
}
static void mcp23s17_set_direction(gpio_exp_t* self) {
// default to input and set real input to generate interrupt
spi_write(self->spi_handle, self->phy.addr, 0x00, ~self->w_mask, 2);
spi_write(self->spi_handle, self->phy.addr, 0x04, self->r_mask, 2);
}
static void mcp23s17_set_pull_mode(gpio_exp_t* self) {
spi_write(self->spi_handle, self->phy.addr, 0x0c, self->pullup, 2);
}
static uint32_t mcp23s17_read(gpio_exp_t* self) {
// read the pins value, not the stored one @interrupt
return spi_read(self->spi_handle, self->phy.addr, 0x12, 2);
}
static void mcp23s17_write(gpio_exp_t* self) {
spi_write(self->spi_handle, self->phy.addr, 0x12, self->shadow, 2);
}
/***************************************************************************************
I2C low level
***************************************************************************************/
/****************************************************************************************
* I2C write up to 32 bits
*/
static esp_err_t i2c_write(uint8_t port, uint8_t addr, uint8_t reg, uint32_t data, int len) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
if (reg != 0xff) i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
// works with our endianness
if (len > 1) i2c_master_write(cmd, (uint8_t*) &data, len, I2C_MASTER_NACK);
else i2c_master_write_byte(cmd, data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "I2C write failed");
}
return ret;
}
/****************************************************************************************
* I2C read up to 32 bits
*/
static uint32_t i2c_read(uint8_t port, uint8_t addr, uint8_t reg, int len) {
uint32_t data = 0;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
// when using a register, write it's value then the device address again
if (reg != 0xff) {
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
} else {
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
}
// works with our endianness
if (len > 1) i2c_master_read(cmd, (uint8_t*) &data, len, I2C_MASTER_LAST_NACK);
else i2c_master_read_byte(cmd, (uint8_t*) &data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(port, cmd, 100 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "I2C read failed");
}
return data;
}
/***************************************************************************************
SPI low level
***************************************************************************************/
/****************************************************************************************
* SPI device addition
*/
static spi_device_handle_t spi_config(struct gpio_exp_phy_s *phy) {
spi_device_interface_config_t config = { };
spi_device_handle_t handle = NULL;
config.command_bits = config.address_bits = 8;
config.clock_speed_hz = phy->speed ? phy->speed : SPI_MASTER_FREQ_8M;
config.spics_io_num = phy->cs_pin;
config.queue_size = 1;
config.flags = SPI_DEVICE_NO_DUMMY;
spi_bus_add_device( phy->host, &config, &handle );
ESP_LOGI(TAG, "SPI expander initialized on host:%d with cs:%d and speed:%dHz", phy->host, phy->cs_pin, config.clock_speed_hz);
return handle;
}
/****************************************************************************************
* SPI write up to 32 bits
*/
static esp_err_t spi_write(spi_device_handle_t handle, uint8_t addr, uint8_t reg, uint32_t data, int len) {
spi_transaction_t transaction = { };
// rx_buffer is NULL, nothing to receive
transaction.flags = SPI_TRANS_USE_TXDATA;
transaction.cmd = addr << 1;
transaction.addr = reg;
transaction.tx_data[0] = data; transaction.tx_data[1] = data >> 8;
transaction.length = len * 8;
// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)
return spi_device_polling_transmit(handle, &transaction);
}
/****************************************************************************************
* SPI read up to 32 bits
*/
static uint32_t spi_read(spi_device_handle_t handle, uint8_t addr, uint8_t reg, int len) {
spi_transaction_t *transaction = heap_caps_calloc(1, sizeof(spi_transaction_t), MALLOC_CAP_DMA);
// tx_buffer is NULL, nothing to transmit except cmd/addr
transaction->flags = SPI_TRANS_USE_RXDATA;
transaction->cmd = (addr << 1) | 0x01;
transaction->addr = reg;
transaction->length = len * 8;
// only do polling as we don't have contention on SPI (otherwise DMA for transfers > 16 bytes)
spi_device_polling_transmit(handle, transaction);
uint32_t data = *(uint32_t*) transaction->rx_data;
free(transaction);
return data;
}

View File

@@ -0,0 +1,61 @@
/* GDS Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#pragma once
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "driver/gpio.h"
struct gpio_exp_s;
typedef struct {
char model[32];
int intr;
uint8_t count;
uint32_t base;
struct gpio_exp_phy_s {
uint8_t addr;
struct { // for I2C
uint8_t port;
};
struct { // for SPI
uint32_t speed;
uint8_t host;
uint8_t cs_pin;
};
} phy;
} gpio_exp_config_t;
// set <intr> to -1 and <queue> to NULL if there is no interrupt
struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config);
uint32_t gpio_exp_get_base(struct gpio_exp_s *expander);
struct gpio_exp_s* gpio_exp_get_expander(int gpio);
#define gpio_is_expanded(gpio) (gpio < GPIO_NUM_MAX)
/*
For all functions below when <expander> is provided, GPIO's can be numbered from 0. If <expander>
is NULL, then GPIO must start from base OR be on-chip
*/
esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander);
esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander);
int gpio_exp_get_level(int gpio, int age, struct gpio_exp_s *expander);
esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander);
esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr, uint32_t debounce, void *arg, struct gpio_exp_s *expander);
esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander);
// unified function to use either built-in or expanded GPIO
esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode);
esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode);
int gpio_get_level_x(int gpio);
esp_err_t gpio_set_level_x(int gpio, int level);
esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args);
esp_err_t gpio_isr_handler_remove_x(int gpio);
#define gpio_set_intr_type_x(gpio, type) do { if (gpio < GPIO_NUM_MAX) gpio_set_intr_type(gpio, type); } while (0)
#define gpio_intr_enable_x(gpio) do { if (gpio < GPIO_NUM_MAX) gpio_intr_enable(gpio); } while (0)
#define gpio_pad_select_gpio_x(gpio) do { if (gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(gpio); } while (0)

View File

@@ -19,6 +19,7 @@
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "platform_config.h"
#include "gpio_exp.h"
#include "led.h"
#include "globdefs.h"
#include "accessors.h"
@@ -40,7 +41,8 @@ static EXT_RAM_ATTR struct led_s {
TimerHandle_t timer;
} leds[MAX_LED];
static EXT_RAM_ATTR struct {
// can't use EXT_RAM_ATTR for initialized structure
static struct {
int gpio;
int active;
int pwm;
@@ -53,7 +55,7 @@ static int led_max = 2;
*
*/
static void set_level(struct led_s *led, bool on) {
if (led->pwm < 0) gpio_set_level(led->gpio, on ? led->onstate : !led->onstate);
if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_x(led->gpio, on ? led->onstate : !led->onstate);
else {
ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
@@ -179,9 +181,9 @@ bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
leds[idx].onstate = onstate;
leds[idx].pwm = -1;
if (pwm < 0) {
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
if (pwm < 0 || gpio >= GPIO_NUM_MAX) {
gpio_pad_select_gpio_x(gpio);
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
} else {
leds[idx].channel = pwm_system.base_channel++;
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
@@ -232,10 +234,10 @@ void led_svc_init(void) {
parse_set_GPIO(set_led_gpio);
#endif
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p;
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
if (nvs_item) {
if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1);
PARSE_PARAM(nvs_item, "green", '=', green.pwm);
PARSE_PARAM(nvs_item, "red", '=', red.pwm);
free(nvs_item);
}

View File

@@ -145,7 +145,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
*
*/
static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGD(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
ESP_LOGI(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
if (jack_handler_svc) (*jack_handler_svc)(event == BUTTON_PRESSED);
}

View File

@@ -91,6 +91,7 @@
#include "esp_log.h"
#include "driver/gpio.h"
#include "gpio_exp.h"
#define TAG "rotary_encoder"
@@ -148,7 +149,7 @@ static uint8_t _process(rotary_encoder_info_t * info)
if (info != NULL)
{
// Get state of input pins.
uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
uint8_t pin_state = (gpio_get_level_x(info->pin_b) << 1) | gpio_get_level_x(info->pin_a);
// Determine new state from the pins and state table.
#ifdef ROTARY_ENCODER_DEBUG
@@ -198,12 +199,18 @@ static void _isr_rotenc(void * args)
.direction = info->state.direction,
},
};
BaseType_t task_woken = pdFALSE;
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
if (task_woken)
{
portYIELD_FROM_ISR();
}
if (info->pin_a < GPIO_NUM_MAX) {
BaseType_t task_woken = pdFALSE;
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
if (task_woken)
{
portYIELD_FROM_ISR();
}
}
else
{
xQueueOverwrite(info->queue, &queue_event);
}
}
}
@@ -220,19 +227,19 @@ esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gp
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
// configure GPIOs
gpio_pad_select_gpio(info->pin_a);
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
gpio_pad_select_gpio_x(info->pin_a);
gpio_set_pull_mode_x(info->pin_a, GPIO_PULLUP_ONLY);
gpio_set_direction_x(info->pin_a, GPIO_MODE_INPUT);
gpio_set_intr_type_x(info->pin_a, GPIO_INTR_ANYEDGE);
gpio_pad_select_gpio(info->pin_b);
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
gpio_pad_select_gpio_x(info->pin_b);
gpio_set_pull_mode_x(info->pin_b, GPIO_PULLUP_ONLY);
gpio_set_direction_x(info->pin_b, GPIO_MODE_INPUT);
gpio_set_intr_type_x(info->pin_b, GPIO_INTR_ANYEDGE);
// install interrupt handlers
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
gpio_isr_handler_add_x(info->pin_a, _isr_rotenc, info);
gpio_isr_handler_add_x(info->pin_b, _isr_rotenc, info);
}
else
{
@@ -280,8 +287,8 @@ esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
esp_err_t err = ESP_OK;
if (info)
{
gpio_isr_handler_remove(info->pin_a);
gpio_isr_handler_remove(info->pin_b);
gpio_isr_handler_remove_x(info->pin_a);
gpio_isr_handler_remove_x(info->pin_b);
}
else
{

View File

@@ -12,6 +12,7 @@
#include "driver/ledc.h"
#include "driver/i2c.h"
#include "platform_config.h"
#include "gpio_exp.h"
#include "battery.h"
#include "led.h"
#include "monitor.h"
@@ -38,8 +39,11 @@ static const char *TAG = "services";
/****************************************************************************************
*
*/
void set_power_gpio(int gpio, char *value) {
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);
@@ -49,9 +53,26 @@ void set_power_gpio(int gpio, char *value) {
gpio_pad_select_gpio(gpio);
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
gpio_set_level(gpio, 0);
} else parsed = false ;
} else parsed = false;
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);
} else if (!strcasecmp(value, "gnd")) {
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);
}
@@ -69,8 +90,8 @@ void services_init(void) {
}
#endif
// set potential power GPIO
parse_set_GPIO(set_power_gpio);
// set potential power GPIO on chip first in case expanders are power using these
parse_set_GPIO(set_chip_power_gpio);
// shared I2C bus
const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
@@ -85,7 +106,7 @@ void services_init(void) {
}
const spi_bus_config_t * spi_config = config_spi_get((spi_host_device_t*) &spi_system_host);
ESP_LOGI(TAG,"Configuring SPI data:%d clk:%d host:%u dc:%d", spi_config->mosi_io_num, spi_config->sclk_io_num, spi_system_host, spi_system_dc_gpio);
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 );
@@ -100,6 +121,13 @@ void services_init(void) {
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
parse_set_GPIO(set_exp_power_gpio);
// system-wide PWM timer configuration
ledc_timer_config_t pwm_timer = {
.duty_resolution = LEDC_TIMER_13_BIT,

View File

@@ -15,19 +15,10 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978
EMBED_FILES vu.data
)
set_source_files_properties(mad.c
set_source_files_properties(mad.c pcm.c flac.c alac.c helix-aac.c vorbis.c opus.c
PROPERTIES COMPILE_FLAGS
-Wno-maybe-uninitialized
)
set_source_files_properties(pcm.c
PROPERTIES COMPILE_FLAGS
-Wno-maybe-uninitialized
)
set_source_files_properties(flac.c
PROPERTIES COMPILE_FLAGS
-Wno-maybe-uninitialized
)
add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DCUSTOM_VERSION=${BUILD_NUMBER})

View File

@@ -33,6 +33,7 @@ extern const struct adac_s dac_external;
int adac_init(char *config, int i2c_port);
void adac_deinit(void);
esp_err_t adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count);
esp_err_t adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val);
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val);
uint8_t adac_read_byte(int i2c_addr, uint8_t reg);

View File

@@ -17,6 +17,11 @@
#include "esp_log.h"
#include "adac.h"
#define PARSE_PARAM(S,P,C,V) do { \
char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); \
} while (0)
static const char TAG[] = "DAC core";
static int i2c_port = -1;
@@ -46,9 +51,9 @@ int adac_init(char *config, int i2c_port_num) {
.master.clk_speed = 250000,
};
if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "i2c", '=', i2c_addr);
PARSE_PARAM(config, "sda", '=', i2c_config.sda_io_num);
PARSE_PARAM(config, "scl", '=', i2c_config.scl_io_num);
if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
ESP_LOGW(TAG, "DAC does not use i2c");
@@ -150,8 +155,7 @@ uint16_t adac_read_word(int i2c_addr, uint8_t reg) {
/****************************************************************************************
*
*/
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val)
{
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val) {
uint8_t data[] = { i2c_addr << 1, reg,
val >> 8, val & 0xff };
@@ -169,4 +173,26 @@ esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val)
}
return ret;
}
}
/****************************************************************************************
*
*/
esp_err_t adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
i2c_master_write(cmd, data, count, I2C_MASTER_NACK);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 200 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "I2C write failed");
}
return ret;
}

View File

@@ -73,13 +73,11 @@ void _buf_flush(struct buffer *buf) {
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
void buf_adjust(struct buffer *buf, size_t mod) {
size_t size;
mutex_lock(buf->mutex);
size = ((unsigned)(buf->base_size / mod)) * mod;
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = ((size_t)(buf->size / mod)) * mod;
buf->readp = buf->writep = buf->buf;
buf->wrap = buf->buf + buf->base_size;
buf->size = buf->base_size;
mutex_unlock(buf->mutex);
}
@@ -90,16 +88,23 @@ void _buf_resize(struct buffer *buf, size_t size) {
buf->buf = malloc(size);
if (!buf->buf) {
size = buf->size;
buf->buf= malloc(size);
if (!buf->buf) {
size = 0;
}
buf->buf = malloc(size);
if (!buf->buf) size = 0;
}
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->writep = buf->readp = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
buf->true_size = buf->base_size = buf->size = size;
}
size_t _buf_limit(struct buffer *buf, size_t limit) {
if (limit) {
buf->size = limit;
buf->readp = buf->writep = buf->buf;
} else {
buf->size = buf->base_size;
}
buf->wrap = buf->buf + buf->size;
return buf->base_size - buf->size;
}
void _buf_unwrap(struct buffer *buf, size_t cont) {
@@ -130,7 +135,9 @@ void _buf_unwrap(struct buffer *buf, size_t cont) {
if (len > by) {
memmove(buf->buf, buf->buf + by, len - by);
buf->writep -= by;
} else buf->writep += buf->size - by;
} else {
buf->writep += buf->size - by;
}
return;
}
@@ -157,8 +164,7 @@ void buf_init(struct buffer *buf, size_t size) {
buf->readp = buf->buf;
buf->writep = buf->buf;
buf->wrap = buf->buf + size;
buf->size = size;
buf->base_size = size;
buf->true_size = buf->base_size = buf->size = size;
mutex_create_p(buf->mutex);
}
@@ -166,8 +172,7 @@ void buf_destroy(struct buffer *buf) {
if (buf->buf) {
free(buf->buf);
buf->buf = NULL;
buf->size = 0;
buf->base_size = 0;
buf->size = buf->base_size = buf->true_size = 0;
mutex_destroy(buf->mutex);
}
}

View File

@@ -218,7 +218,6 @@ static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
// close existing CLI connection and open new one
if (cli_sock >= 0) closesocket(cli_sock);
cli_sock = socket(AF_INET, SOCK_STREAM, 0);
connect_cli_socket();
LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport);

View File

@@ -9,26 +9,22 @@
*
*/
#include <math.h>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#endif
#include "platform_config.h"
#include "squeezelite.h"
#if CONFIG_BT_SINK
#include "bt_app_sink.h"
#include "raop_sink.h"
#include <math.h>
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
enum { DECODE_BT = 1, DECODE_RAOP };
extern struct outputstate output;
extern struct decodestate decode;
extern struct buffer *outputbuf;
// this is the only system-wide loglevel variable
extern log_level loglevel;
static bool enable_bt_sink;
#endif
#if CONFIG_AIRPLAY_SINK
#include "raop_sink.h"
static bool enable_airplay;
#define RAOP_OUTPUT_SIZE (((RAOP_SAMPLE_RATE * BYTES_PER_FRAME * 2 * 120) / 100) & ~BYTES_PER_FRAME)
@@ -44,6 +40,22 @@ static EXT_RAM_ATTR struct {
s32_t len;
u32_t start_time, playtime;
} raop_sync;
#endif
static bool abort_sink ;
#define LOCK_O mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#define LOCK_D mutex_lock(decode.mutex);
#define UNLOCK_D mutex_unlock(decode.mutex);
enum { DECODE_BT = 1, DECODE_RAOP};
extern struct outputstate output;
extern struct decodestate decode;
extern struct buffer *outputbuf;
// this is the only system-wide loglevel variable
extern log_level loglevel;
/****************************************************************************************
* Common sink data handler
@@ -58,11 +70,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
return;
}
// there will always be room at some point
while (len) {
LOCK_O;
LOCK_O;
abort_sink = false;
// there will always be room at some point
while (len && wait && !abort_sink) {
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
bytes = min(len, bytes);
#if BYTES_PER_FRAME == 4
@@ -81,11 +94,14 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
len -= bytes;
data += bytes;
UNLOCK_O;
// allow i2s to empty the buffer if needed
if (len && !space && wait--) usleep(20000);
if (len && !space) {
wait--;
UNLOCK_O; usleep(50000); LOCK_O;
}
}
UNLOCK_O;
if (!wait) {
LOG_WARN("Waited too long, dropping frames");
@@ -95,12 +111,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
/****************************************************************************************
* BT sink command handler
*/
#if CONFIG_BT_SINK
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use BT sink while LMS/AirPlay is controlling player");
LOG_WARN("Cannot use BT sink while LMS/AirPlay are controlling player");
return false;
}
@@ -110,34 +126,37 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
switch(cmd) {
case BT_SINK_AUDIO_STARTED:
_buf_flush(outputbuf);
_buf_limit(outputbuf, 0);
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
output.external = DECODE_BT;
output.state = OUTPUT_STOPPED;
output.frames_played = 0;
_buf_flush(outputbuf);
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("BT sink started");
break;
case BT_SINK_AUDIO_STOPPED:
if (output.external == DECODE_BT) {
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
output.external = 0;
output.stop_time = gettime_ms();
LOG_INFO("BT sink stopped");
}
break;
case BT_SINK_PLAY:
output.state = OUTPUT_RUNNING;
LOG_INFO("BT playing");
LOG_INFO("BT play");
break;
case BT_SINK_STOP:
_buf_flush(outputbuf);
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
LOG_INFO("BT stopped");
abort_sink = true;
LOG_INFO("BT stop");
break;
case BT_SINK_PAUSE:
output.stop_time = gettime_ms();
LOG_INFO("BT paused, just silence");
LOG_INFO("BT pause, just silence");
break;
case BT_SINK_RATE:
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
@@ -158,10 +177,12 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
return true;
}
#endif
/****************************************************************************************
* raop sink data handler
*/
#if CONFIG_AIRPLAY_SINK
static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t playtime) {
raop_sync.playtime = playtime;
@@ -177,7 +198,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
{
// don't LOCK_O as there is always a chance that LMS takes control later anyway
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
LOG_WARN("Cannot use Airplay sink while LMS/BT is controlling player");
LOG_WARN("Cannot use Airplay sink while LMS/BT are controlling player");
return false;
}
@@ -237,15 +258,21 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
break;
}
case RAOP_SETUP:
// we need a fair bit of space for RTP process
_buf_resize(outputbuf, RAOP_OUTPUT_SIZE);
case RAOP_SETUP: {
uint8_t **buffer = va_arg(args, uint8_t**);
size_t *size = va_arg(args, size_t*);
// steal buffer tail from outputbuf but do not reallocate
*size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
*buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
output.frames_played = 0;
output.external = DECODE_RAOP;
output.state = OUTPUT_STOPPED;
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
LOG_INFO("resizing buffer %u", outputbuf->size);
break;
}
case RAOP_STREAM:
LOG_INFO("Stream", NULL);
raop_state = event;
@@ -256,12 +283,14 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
break;
case RAOP_STOP:
output.external = 0;
__attribute__ ((fallthrough));
case RAOP_FLUSH:
if (event == RAOP_FLUSH) { LOG_INFO("Flush", NULL); }
else { LOG_INFO("Stop", NULL); }
LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
_buf_flush(outputbuf);
raop_state = event;
_buf_flush(outputbuf);
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
abort_sink = true;
output.frames_played = 0;
output.stop_time = gettime_ms();
break;
@@ -292,6 +321,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
UNLOCK_D;
return true;
}
#endif
/****************************************************************************************
* We provide the generic codec register option
@@ -299,50 +329,55 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
void register_external(void) {
char *p;
#if CONFIG_BT_SINK
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_bt_sink")) != NULL) {
enable_bt_sink = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
enable_bt_sink = !strcmp(p,"1") || !strcasecmp(p,"y");
free(p);
}
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_airplay")) != NULL) {
enable_airplay = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
free(p);
}
if (!strcasestr(output.device, "BT ") ) {
if(enable_bt_sink){
if (!strcasestr(output.device, "BT") && enable_bt_sink) {
bt_sink_init(bt_sink_cmd_handler, sink_data_handler);
LOG_INFO("Initializing BT sink");
}
} else {
LOG_WARN("Cannot be a BT sink and source");
}
if (enable_airplay){
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
LOG_INFO("Initializing AirPlay sink");
}
}
#endif
#if CONFIG_AIRPLAY_SINK
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_airplay")) != NULL) {
enable_airplay = !strcmp(p,"1") || !strcasecmp(p,"y");
free(p);
if (enable_airplay){
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
LOG_INFO("Initializing AirPlay sink");
}
}
#endif
}
void deregister_external(void) {
if (!strcasestr(output.device, "BT ") && enable_bt_sink) {
LOG_INFO("Stopping BT sink");
#if CONFIG_BT_SINK
if (!strcasestr(output.device, "BT") && enable_bt_sink) {
bt_sink_deinit();
}
#endif
#if CONFIG_AIRPLAY_SINK
if (enable_airplay){
LOG_INFO("Stopping AirPlay sink");
raop_sink_deinit();
}
#endif
}
void decode_restore(int external) {
switch (external) {
#if CONFIG_BT_SINK
case DECODE_BT:
bt_disconnect();
break;
#endif
#if CONFIG_AIRPLAY_SINK
case DECODE_RAOP:
raop_disconnect();
raop_state = RAOP_STOP;
break;
#endif
}
}

View File

@@ -352,7 +352,7 @@ bool sb_displayer_init(void) {
// create displayer management task
displayer.mutex = xSemaphoreCreateMutex();
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "squeeze_displayer", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "sb_displayer", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
// chain handlers
slimp_handler_chain = slimp_handler;
@@ -1292,13 +1292,14 @@ static void displayer_task(void *args) {
}
// update visu if active
if ((visu.mode || led_visu.mode) && displayer.wake <= 0) {
if ((visu.mode || led_visu.mode) && displayer.wake <= 0 && displayer.owned) {
displayer_update();
displayer.wake = 100;
}
// need to make sure we own display
if (display && displayer.owned) GDS_Update(display);
else if (!led_display) displayer.wake = LONG_WAKE;
// release semaphore and sleep what's needed
xSemaphoreGive(displayer.mutex);

View File

@@ -20,8 +20,8 @@
static const char TAG[] = "DAC external";
static void speaker(bool active) { }
static void headset(bool active) { }
static void speaker(bool active);
static void headset(bool active);
static bool volume(unsigned left, unsigned right) { return false; }
static void power(adac_power_e mode);
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
@@ -95,6 +95,22 @@ static void power(adac_power_e mode) {
else i2c_json_execute("poweron");
}
/****************************************************************************************
* speaker
*/
static void speaker(bool active) {
if (active) i2c_json_execute("speakeron");
else i2c_json_execute("speakeroff");
}
/****************************************************************************************
* headset
*/
static void headset(bool active) {
if (active) i2c_json_execute("headseton");
else i2c_json_execute("headsetoff");
}
/****************************************************************************************
*
*/
@@ -104,25 +120,40 @@ bool i2c_json_execute(char *set) {
if (!json_set) return true;
cJSON_ArrayForEach(item, json_set)
{
cJSON_ArrayForEach(item, json_set) {
cJSON *reg = cJSON_GetObjectItemCaseSensitive(item, "reg");
cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
if (cJSON_IsArray(val)) {
cJSON *value;
uint8_t *data = malloc(cJSON_GetArraySize(val));
int count = 0;
if (!data) continue;
cJSON_ArrayForEach(value, val) {
data[count++] = value->valueint;
}
adac_write(i2c_addr, reg->valueint, data, count);
free(data);
} else {
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
if (!reg || !val) continue;
if (!reg || !val) continue;
if (!mode) {
adac_write_byte(i2c_addr, reg->valueint, val->valueint);
} else if (!strcasecmp(mode->valuestring, "or")) {
uint8_t data = adac_read_byte(i2c_addr,reg->valueint);
data |= (uint8_t) val->valueint;
adac_write_byte(i2c_addr, reg->valueint, data);
} else if (!strcasecmp(mode->valuestring, "and")) {
uint8_t data = adac_read_byte(i2c_addr, reg->valueint);
data &= (uint8_t) val->valueint;
adac_write_byte(i2c_addr, reg->valueint, data);
}
if (!mode) {
adac_write_byte(i2c_addr, reg->valueint, val->valueint);
} else if (!strcasecmp(mode->valuestring, "or")) {
uint8_t data = adac_read_byte(i2c_addr,reg->valueint);
data |= (uint8_t) val->valueint;
adac_write_byte(i2c_addr, reg->valueint, data);
} else if (!strcasecmp(mode->valuestring, "and")) {
uint8_t data = adac_read_byte(i2c_addr, reg->valueint);
data &= (uint8_t) val->valueint;
adac_write_byte(i2c_addr, reg->valueint, data);
}
}
}
return true;

View File

@@ -354,7 +354,6 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
loglevel = level;
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
output.init_size = output_buf_size;
LOG_DEBUG("outputbuf size: %u", output_buf_size);
buf_init(outputbuf, output_buf_size);

View File

@@ -81,12 +81,14 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
output.start_frames = FRAME_BLOCK;
output.rate_delay = rate_delay;
if (strcasestr(device, "BT ") || !strcasecmp(device, "BT")) {
#if CONFIG_BT_SINK
if (strcasestr(device, "BT")) {
LOG_INFO("init Bluetooth");
close_cb = &output_close_bt;
output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle);
} else {
} else
#endif
{
LOG_INFO("init I2S/SPDIF");
close_cb = &output_close_i2s;
volume_cb = &output_volume_i2s;

View File

@@ -43,6 +43,7 @@ sure that using rate_delay would fix that
#include "led.h"
#include "monitor.h"
#include "platform_config.h"
#include "gpio_exp.h"
#include "accessors.h"
#include "equalizer.h"
#include "globdefs.h"
@@ -129,8 +130,13 @@ static bool handler(u8_t *data, int len){
jack_mutes_amp = pkt->config == 0;
config_set_value(NVS_TYPE_STR, "jack_mutes_amp", jack_mutes_amp ? "y" : "n");
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
else adac->speaker(true);
if (jack_mutes_amp && jack_inserted_svc()) {
adac->speaker(false);
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
} else {
adac->speaker(true);
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active);
}
}
LOG_INFO("got AUDO %02x", pkt->config);
@@ -151,13 +157,12 @@ static void jack_handler(bool inserted) {
// jack detection bounces a bit but that seems fine
if (jack_mutes_amp) {
LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
if (inserted) adac->speaker(false);
else adac->speaker(true);
adac->speaker(!inserted);
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, inserted ? !amp_control.active : amp_control.active);
}
// activate headset
if (inserted) adac->headset(true);
else adac->headset(false);
adac->headset(inserted);
// and chain if any
if (jack_handler_chain) (jack_handler_chain)(inserted);
@@ -173,9 +178,9 @@ static void set_amp_gpio(int gpio, char *value) {
amp_control.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
gpio_pad_select_gpio(amp_control.gpio);
gpio_set_direction(amp_control.gpio, GPIO_MODE_OUTPUT);
gpio_set_level(amp_control.gpio, !amp_control.active);
gpio_pad_select_gpio_x(amp_control.gpio);
gpio_set_direction_x(amp_control.gpio, GPIO_MODE_OUTPUT);
gpio_set_level_x(amp_control.gpio, !amp_control.active);
LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active);
}
@@ -185,11 +190,10 @@ static void set_amp_gpio(int gpio, char *value) {
* Set pin from config string
*/
static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
char *p;
pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1;
if ((p = strcasestr(config, "bck")) != NULL) pin_config->bck_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "ws")) != NULL) pin_config->ws_io_num = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "do")) != NULL) pin_config->data_out_num = atoi(strchr(p, '=') + 1);
PARSE_PARAM(config, "bck", '=', pin_config->bck_io_num);
PARSE_PARAM(config, "ws", '=', pin_config->ws_io_num);
PARSE_PARAM(config, "do", '=', pin_config->data_out_num);
}
/****************************************************************************************
@@ -278,7 +282,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
res = i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_spdif_pin);
LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_spdif_pin.bck_io_num, i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
LOG_INFO("SPDIF using I2S bck:%d, ws:%d, do:%d", i2s_spdif_pin.bck_io_num, i2s_spdif_pin.ws_io_num, i2s_spdif_pin.data_out_num);
} else {
i2s_config.sample_rate = output.current_sample_rate;
i2s_config.bits_per_sample = BYTES_PER_FRAME * 8 / 2;
@@ -343,12 +347,13 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
jack_handler_chain = jack_handler_svc;
jack_handler_svc = jack_handler;
parse_set_GPIO(set_amp_gpio);
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
else adac->speaker(true);
adac->headset(jack_inserted_svc());
parse_set_GPIO(set_amp_gpio);
// create task as a FreeRTOS task but uses stack in internal RAM
{
@@ -365,10 +370,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
// memory still used but at least task is not created
if (stats) {
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
// we allocate TCB but stack is static to avoid SPIRAM fragmentation
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE,
NULL, ESP_TASK_PRIO_MIN, xStack, &xTaskBuffer);
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
}
}
@@ -449,13 +455,16 @@ static void output_thread_i2s(void *arg) {
LOG_INFO("Output state is %d", output.state);
if (output.state == OUTPUT_OFF) {
led_blink(LED_GREEN, 100, 2500);
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, !amp_control.active);
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
} else if (output.state == OUTPUT_STOPPED) {
adac->speaker(false);
led_blink(LED_GREEN, 200, 1000);
} else if (output.state == OUTPUT_RUNNING) {
if (!jack_mutes_amp || !jack_inserted_svc()) adac->speaker(true);
if (!jack_mutes_amp || !jack_inserted_svc()) {
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active);
adac->speaker(true);
}
led_on(LED_GREEN);
}
}
@@ -513,7 +522,6 @@ static void output_thread_i2s(void *arg) {
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
i2s_start(CONFIG_I2S_NUM);
adac->power(ADAC_ON);
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, amp_control.active);
}
// this does not work well as set_sample_rates resets the fifos (and it's too early)

View File

@@ -387,11 +387,14 @@ static void process_strm(u8_t *pkt, int len) {
}
sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false;
LOCK_O;
#if EMBEDDED
// not protected so that restore can call synchronously sink handlers
if (output.external) decode_restore(output.external);
LOCK_O;
output.external = 0;
_buf_resize(outputbuf, output.init_size);
_buf_limit(outputbuf, 0);
#else
LOCK_O;
#endif
output.threshold = strm->output_threshold;
output.next_replay_gain = unpackN(&strm->replay_gain);

View File

@@ -532,6 +532,7 @@ struct buffer {
u8_t *wrap;
size_t size;
size_t base_size;
size_t true_size;
mutex_type mutex;
};
@@ -547,6 +548,7 @@ void _buf_flush(struct buffer *buf);
void _buf_unwrap(struct buffer *buf, size_t cont);
void buf_adjust(struct buffer *buf, size_t mod);
void _buf_resize(struct buffer *buf, size_t size);
size_t _buf_limit(struct buffer *buf, size_t limit);
void buf_init(struct buffer *buf, size_t size);
void buf_destroy(struct buffer *buf);
@@ -665,7 +667,6 @@ struct outputstate {
u8_t channels;
const char *device;
int external;
u32_t init_size;
#if ALSA
unsigned buffer;
unsigned period;

View File

@@ -46,51 +46,47 @@
* Globals
*/
#define TELNET_STACK_SIZE 8048
#define TELNET_STACK_SIZE 4096
#define TELNET_RX_BUF 1024
const static char TAG[] = "telnet";
static int uart_fd=0;
RingbufHandle_t buf_handle;
static size_t send_chunk=300;
static size_t log_buf_size=2000; //32-bit aligned size
static bool bIsEnabled=false;
static int partnerSocket=0;
static telnet_t *tnHandle;
extern bool bypass_wifi_manager;
/************************************
* Forward declarations
*/
static void telnet_task(void *data);
static ssize_t stdout_read(int fd, void* data, size_t size);
static int stdout_open(const char * path, int flags, int mode);
static int stdout_close(int fd);
static int stdout_fstat(int fd, struct stat * st);
static ssize_t stdout_write(int fd, const void * data, size_t size);
static char *eventToString(telnet_event_type_t type);
static void handle_telnet_conn();
static void process_logs( UBaseType_t bytes, bool is_write_op);
static bool bMirrorToUART=false;
struct telnetUserData {
int sockfd;
telnet_t *tnHandle;
char * rxbuf;
};
bool is_serial_suppressed(){
return bIsEnabled?!bMirrorToUART:false ;
}
const static char TAG[] = "telnet";
static int uart_fd;
static RingbufHandle_t buf_handle;
static size_t send_chunk = 512;
static size_t log_buf_size = 4*1024;
static bool bIsEnabled=false;
static int partnerSocket;
static telnet_t *tnHandle;
static bool bMirrorToUART;
/************************************
* Forward declarations
*/
static void telnet_task(void *data);
static int stdout_open(const char * path, int flags, int mode);
static int stdout_fstat(int fd, struct stat * st);
static ssize_t stdout_write(int fd, const void * data, size_t size);
static void handle_telnet_conn();
static size_t process_logs( UBaseType_t bytes, bool make_room);
void init_telnet(){
char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable");
if (!val || strlen(val) == 0 || !strcasestr("YXD",val) ) {
ESP_LOGI(TAG,"Telnet support disabled");
if(val) free(val);
return;
}
// if wifi manager is bypassed, there will possibly be no wifi available
//
bMirrorToUART = (strcasestr("D",val)!=NULL);
if(!bMirrorToUART && bypass_wifi_manager){
// This isn't supposed to happen, as telnet won't start if wifi manager isn't
@@ -99,20 +95,20 @@ void init_telnet(){
}
FREE_AND_NULL(val);
val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
if(val){
send_chunk=atol(val);
val = get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
if (val){
int size = atol(val);
if (size > 0) send_chunk = size;
free(val);
send_chunk=send_chunk>0?send_chunk:500;
}
val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_buffer");
if(val){
log_buf_size=atol(val);
val = get_nvs_value_alloc(NVS_TYPE_STR, "telnet_buffer");
if (val){
int size = atol(val);
if (size > 0) log_buf_size = size;
free(val);
log_buf_size=log_buf_size>0?log_buf_size:4000;
}
// Redirect the output to our telnet handler as soon as possible
StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)malloc(sizeof(StaticRingbuffer_t) );
StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *) heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
// All non-split ring buffer must have their memory alignment set to 32 bits.
uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT );
buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
@@ -124,281 +120,189 @@ void init_telnet(){
}
ESP_LOGI(TAG, "***Redirecting log output to telnet");
const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.write = &stdout_write,
.open = &stdout_open,
.fstat = &stdout_fstat,
.close = &stdout_close,
.read = &stdout_read,
esp_vfs_t vfs = { };
vfs.flags = ESP_VFS_FLAG_DEFAULT;
vfs.write = &stdout_write;
vfs.open = &stdout_open;
vfs.fstat = &stdout_fstat;
};
if (bMirrorToUART) uart_fd = open("/dev/uart/0", O_RDWR);
if(bMirrorToUART){
uart_fd=open("/dev/uart/0", O_RDWR);
}
ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
freopen("/dev/pkspstdout", "w", stdout);
freopen("/dev/pkspstdout", "w", stderr);
freopen("/dev/pkspstdout", "wb", stdout);
freopen("/dev/pkspstdout", "wb", stderr);
bIsEnabled=true;
}
void start_telnet(void * pvParameter){
static bool isStarted=false;
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
if (isStarted || !bIsEnabled) return;
isStarted=true;
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
StackType_t *xStack = heap_caps_malloc(TELNET_STACK_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if(!isStarted && bIsEnabled) {
xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
isStarted=true;
}
xTaskCreateStatic( (TaskFunction_t) &telnet_task, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
}
static void telnet_task(void *data) {
int serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int serverSocket;
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(23);
int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
if (rc < 0) {
ESP_LOGE(TAG, "bind: %d (%s)", errno, strerror(errno));
close(serverSocket);
return;
while (1) {
serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) >= 0 && listen(serverSocket, 1) >= 0) break;
close(serverSocket);
ESP_LOGI(TAG, "can't bind Telnet socket");
vTaskDelay(pdMS_TO_TICKS(1000));
}
rc = listen(serverSocket, 5);
if (rc < 0) {
ESP_LOGE(TAG, "listen: %d (%s)", errno, strerror(errno));
close(serverSocket);
return;
}
while(1) {
while (1) {
socklen_t len = sizeof(serverAddr);
rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
if (rc < 0 ){
ESP_LOGE(TAG, "accept: %d (%s)", errno, strerror(errno));
return;
}
else {
partnerSocket = rc;
ESP_LOGD(TAG, "We have a new client connection!");
int sock = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
if (sock >= 0) {
partnerSocket = sock;
ESP_LOGI(TAG, "We have a new client connection %d", sock);
handle_telnet_conn();
ESP_LOGD(TAG, "Telnet connection terminated");
ESP_LOGI(TAG, "Telnet connection terminated %d", sock);
} else {
ESP_LOGW(TAG, "accept: %d (%s)", errno, strerror(errno));
}
}
// we should not be here
close(serverSocket);
vTaskDelete(NULL);
}
/**
* Convert a telnet event type to its string representation.
*/
static char *eventToString(telnet_event_type_t type) {
switch(type) {
case TELNET_EV_COMPRESS:
return "TELNET_EV_COMPRESS";
case TELNET_EV_DATA:
return "TELNET_EV_DATA";
case TELNET_EV_DO:
return "TELNET_EV_DO";
case TELNET_EV_DONT:
return "TELNET_EV_DONT";
case TELNET_EV_ENVIRON:
return "TELNET_EV_ENVIRON";
case TELNET_EV_ERROR:
return "TELNET_EV_ERROR";
case TELNET_EV_IAC:
return "TELNET_EV_IAC";
case TELNET_EV_MSSP:
return "TELNET_EV_MSSP";
case TELNET_EV_SEND:
return "TELNET_EV_SEND";
case TELNET_EV_SUBNEGOTIATION:
return "TELNET_EV_SUBNEGOTIATION";
case TELNET_EV_TTYPE:
return "TELNET_EV_TTYPE";
case TELNET_EV_WARNING:
return "TELNET_EV_WARNING";
case TELNET_EV_WILL:
return "TELNET_EV_WILL";
case TELNET_EV_WONT:
return "TELNET_EV_WONT";
case TELNET_EV_ZMP:
return "TELNET_EV_ZMP";
}
return "Unknown type";
} // eventToString
/**
* Telnet handler.
*/
void process_received_data(const char * buffer, size_t size){
//ESP_LOGD(tag, "received data, len=%d", event->data.size);
char * command = malloc(size+1);
const char * c=buffer;
// scrub from any escape command
if(*c == '\e') while (size && size-- && *c++ != '\n');
memcpy(command,c,size);
command[size]='\0';
if(command[0]!='\r' && command[0]!='\n'){
// echo the command buffer out to uart and run
if(bMirrorToUART){
write(uart_fd, command, size);
}
for(int i=strlen(command);i>=0;i--){
// strip any cr/lf
if(command[i]== '\n' || command[i]== '\r') command[i]= '\0';
}
run_command((char *)command);
}
free(command);
}
static void handle_telnet_events(
telnet_t *thisTelnet,
telnet_event_t *event,
void *userData) {
int rc;
static void handle_telnet_events(telnet_t *thisTelnet, telnet_event_t *event, void *userData) {
struct telnetUserData *telnetUserData = (struct telnetUserData *)userData;
switch(event->type) {
case TELNET_EV_SEND:
rc = send(telnetUserData->sockfd, event->data.buffer, event->data.size, 0);
if (rc < 0) {
//printf("ERROR: (telnet) send: %d (%s)", errno, strerror(errno));
}
send(telnetUserData->sockfd, event->data.buffer, event->data.size, 0);
break;
case TELNET_EV_DATA:
process_received_data(event->data.buffer, event->data.size);
console_push(event->data.buffer, event->data.size);
break;
case TELNET_EV_TTYPE:
printf("telnet event: %s\n", eventToString(event->type));
telnet_ttype_send(telnetUserData->tnHandle);
break;
default:
printf("telnet event: %s\n", eventToString(event->type));
break;
} // End of switch event type
} // myhandle_telnet_events
static void process_logs( UBaseType_t bytes, bool is_write_op){
//Receive an item from no-split ring buffer
size_t item_size;
UBaseType_t uxItemsWaiting;
UBaseType_t uxBytesToSend=bytes;
vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &uxItemsWaiting);
bool is_space_available = ((log_buf_size-uxItemsWaiting)>=bytes && log_buf_size>uxItemsWaiting);
if( is_space_available && (is_write_op || partnerSocket == 0) ){
// there's still some room left in the buffer, and we're either
// processing a write operation or telnet isn't connected yet.
return;
}
if(is_write_op && !is_space_available && uxBytesToSend==0){
// flush at least the size of a full chunk
uxBytesToSend = send_chunk;
}
static size_t process_logs(UBaseType_t bytes, bool make_room){
UBaseType_t pending;
vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, NULL, &pending);
// nothing to do or we can do
if (!partnerSocket || (make_room && log_buf_size - pending > bytes)) return pending;
// can't send more than what we have
if (bytes > pending) bytes = pending;
while (bytes > 0) {
size_t size;
char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &size, pdMS_TO_TICKS(50), bytes);
if (!item || !partnerSocket) break;
bytes -= size;
telnet_send_text(tnHandle, item, size);
vRingbufferReturnItem(buf_handle, (void *)item);
}
while(uxBytesToSend>0){
char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend);
//Check received data
if (item != NULL) {
uxBytesToSend-=item_size;
if(partnerSocket!=0){
telnet_send_text(tnHandle, item, item_size);
}
//Return Item
vRingbufferReturnItem(buf_handle, (void *)item);
}
else{
break;
}
}
return pending - bytes;
}
static void handle_telnet_conn() {
static const telnet_telopt_t my_telopts[] = {
{ TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT },
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO },
{ TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT },
{TELNET_TELOPT_LINEMODE, TELNET_WONT, TELNET_DO },
{ -1, 0, 0 }
};
struct telnetUserData *pTelnetUserData = (struct telnetUserData *)heap_caps_malloc(sizeof(struct telnetUserData), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
tnHandle = telnet_init(my_telopts, handle_telnet_events, 0, pTelnetUserData);
static const telnet_telopt_t my_telopts[] = {
{ TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT },
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO },
{ TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO },
{ TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT },
{TELNET_TELOPT_LINEMODE, TELNET_WONT, TELNET_DO },
{ -1, 0, 0 }
};
struct telnetUserData *pTelnetUserData = (struct telnetUserData *)malloc(sizeof(struct telnetUserData));
tnHandle = telnet_init(my_telopts, handle_telnet_events, 0, pTelnetUserData);
pTelnetUserData->rxbuf = (char *) heap_caps_malloc(TELNET_RX_BUF, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
pTelnetUserData->tnHandle = tnHandle;
pTelnetUserData->sockfd = partnerSocket;
pTelnetUserData->rxbuf = (char *) heap_caps_malloc(TELNET_RX_BUF, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
pTelnetUserData->tnHandle = tnHandle;
pTelnetUserData->sockfd = partnerSocket;
bool pending = true;
// flush all the log buffer on connect
process_logs(log_buf_size, false);
while(1) {
fd_set rfds, wfds;
struct timeval timeout = {0, 200*1000};
while(1) {
//ESP_LOGD(tag, "waiting for data");
ssize_t len = recv(partnerSocket, pTelnetUserData->rxbuf, TELNET_RX_BUF, MSG_DONTWAIT);
if (len >0 ) {
telnet_recv(tnHandle, pTelnetUserData->rxbuf, len);
}
else if (errno != EAGAIN && errno !=EWOULDBLOCK ){
telnet_free(tnHandle);
tnHandle = NULL;
free(pTelnetUserData->rxbuf);
pTelnetUserData->rxbuf=NULL;
free(pTelnetUserData);
partnerSocket = 0;
return;
}
process_logs(send_chunk, false);
FD_ZERO(&rfds);
FD_SET(partnerSocket, &rfds);
taskYIELD();
}
FD_ZERO(&wfds);
if (pending) FD_SET(partnerSocket, &wfds);
} // handle_telnet_conn
int res = select(partnerSocket + 1, &rfds, &wfds, NULL, &timeout);
if (res < 0) break;
if (FD_ISSET(partnerSocket, &rfds)) {
int len = recv(partnerSocket, pTelnetUserData->rxbuf, TELNET_RX_BUF, 0);
if (!len) break;
telnet_recv(tnHandle, pTelnetUserData->rxbuf, len);
}
if (FD_ISSET(partnerSocket, &wfds)) {
pending = process_logs(send_chunk, false) > 0;
} else {
pending = true;
}
}
telnet_free(tnHandle);
tnHandle = NULL;
free(pTelnetUserData->rxbuf);
free(pTelnetUserData);
close(partnerSocket);
partnerSocket = 0;
}
// ******************* stdout/stderr Redirection to ringbuffer
static ssize_t stdout_write(int fd, const void * data, size_t size) {
// #1 Write to ringbuffer
if (buf_handle == NULL) {
printf("%s() ABORT. file handle _log_remote_fp is NULL\n",
__FUNCTION__);
} else {
// flush the buffer if needed
// flush the buffer and send item
if (buf_handle) {
process_logs(size, true);
//Send an item
UBaseType_t res = xRingbufferSend(buf_handle, data, size, pdMS_TO_TICKS(10));
assert(res == pdTRUE);
xRingbufferSend(buf_handle, data, size, 0);
}
return bMirrorToUART?write(uart_fd, data, size):size;
}
static ssize_t stdout_read(int fd, void* data, size_t size) {
//return read(fd, data, size);
return 0;
// mirror to uart if required
return (bMirrorToUART || !buf_handle) ? write(uart_fd, data, size) : size;
}
static int stdout_open(const char * path, int flags, int mode) {
return 0;
}
static int stdout_close(int fd) {
return 0;
}
static int stdout_fstat(int fd, struct stat * st) {
st->st_mode = S_IFCHR;
return 0;

View File

@@ -1,4 +1,3 @@
void init_telnet();
void start_telnet(void * pvParameter);
extern bool is_serial_suppressed();

View File

@@ -1,5 +1,5 @@
idf_component_register(SRCS operator.cpp utf8.c
REQUIRES esp_common pthread
idf_component_register(SRCS operator.cpp tools.c
REQUIRES esp_common pthread
INCLUDE_DIRS .
)

View File

@@ -1,3 +1,22 @@
#include <stdlib.h> // for malloc and free
void* operator new(unsigned int size) { return malloc(size); }
void operator delete(void* ptr) { if (ptr) free(ptr); }
#include <memory>
#include <esp_heap_caps.h>
void* operator new(std::size_t count) {
return heap_caps_malloc(count, MALLOC_CAP_SPIRAM);
}
void operator delete(void* ptr) noexcept {
if (ptr) free(ptr);
}
/*
// C++17 only
void* operator new (std::size_t count, std::align_val_t alignment) {
return heap_caps_malloc(count, MALLOC_CAP_SPIRAM);
}
// C++17 only
void operator delete(void* ptr, std::align_val_t alignment) noexcept {
if (ptr) free(ptr);
}
*/

View File

@@ -27,8 +27,8 @@
#define SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"
#endif
extern bool is_recovery_running;
extern esp_err_t run_command(char * line);
extern bool wait_for_wifi();
extern bool console_push(const char * data, size_t size);
extern void console_start();
extern pthread_cond_t wifi_connect_suspend_cond;
extern pthread_t wifi_connect_suspend_mutex;

View File

@@ -1,17 +1,31 @@
/*
* (c) Philippe G. 20201, philippe_44@outlook.com
* see other copyrights below
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include "tools.h"
#include "esp_log.h"
const static char TAG[] = "tools";
/****************************************************************************************
* UTF-8 tools
*/
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
// Copyright (c) 2017 ZephRay <zephray@outlook.com>
//
// utf8to1252 - almost equivalent to iconv -f utf-8 -t windows-1252, but better
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "esp_log.h"
#define TAG "aa"
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
@@ -90,4 +104,27 @@ void utf8_decode(char *src) {
}
*dst = '\0';
}
}
/****************************************************************************************
* URL tools
*/
static inline char from_hex(char ch) {
return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}
void url_decode(char *url) {
char *p, *src = strdup(url);
for (p = src; *src; url++) {
*url = *src++;
if (*url == '%') {
*url = from_hex(*src++) << 4;
*url |= from_hex(*src++);
} else if (*url == '+') {
*url = ' ';
}
}
*url = '\0';
free(p);
}

View File

@@ -10,4 +10,13 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void utf8_decode(char *src);
void url_decode(char *url);
#ifdef __cplusplus
}
#endif

View File

@@ -1,10 +1,9 @@
set( WEBPACK_DIR webapp/webpack/dist )
idf_component_register( SRC_DIRS . webapp
INCLUDE_DIRS . webapp ${IDF_PATH}/components/esp_http_server/src ${IDF_PATH}/components/esp_http_server/src/port/esp32 ${IDF_PATH}/components/esp_http_server/src/util ${IDF_PATH}/components/esp_http_server/src/
REQUIRES squeezelite-ota json mdns
PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console driver_bt
)
idf_component_register( SRC_DIRS . webapp
INCLUDE_DIRS . webapp
REQUIRES squeezelite-ota json mdns
PRIV_REQUIRES tools services platform_config esp_common json newlib freertos spi_flash nvs_flash mdns pthread wpa_supplicant platform_console esp_http_server console driver_bt
)
include(webapp/webapp.cmake)

View File

@@ -1,98 +0,0 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ESP_HTTP_SERVER_H_
#define __ESP_HTTP_SERVER_H_
#include <stdio.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <http_parser.h>
#include <sdkconfig.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Starts the web server
*
* Create an instance of HTTP server and allocate memory/resources for it
* depending upon the specified configuration.
*
* Example usage:
* @code{c}
*
* //Function for starting the webserver
* httpd_handle_t start_webserver(void)
* {
* // Generate default configuration
* httpd_config_t config = HTTPD_DEFAULT_CONFIG();
*
* // Empty handle to http_server
* httpd_handle_t server = NULL;
*
* // Start the httpd server
* if (httpd_start(&server, &config) == ESP_OK) {
* // Register URI handlers
* httpd_register_uri_handler(server, &uri_get);
* httpd_register_uri_handler(server, &uri_post);
* }
* // If server failed to start, handle will be NULL
* return server;
* }
*
* @endcode
*
* @param[in] config Configuration for new instance of the server
* @param[out] handle Handle to newly created instance of the server. NULL on error
* @return
* - ESP_OK : Instance created successfully
* - ESP_ERR_INVALID_ARG : Null argument(s)
* - ESP_ERR_HTTPD_ALLOC_MEM : Failed to allocate memory for instance
* - ESP_ERR_HTTPD_TASK : Failed to launch server task
*/
esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
static inline int __httpd_os_thread_create_static(TaskHandle_t *thread,
const char *name, uint16_t stacksize, int prio,
void (*thread_routine)(void *arg), void *arg,
BaseType_t core_id)
{
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
//
*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
if (*thread) {
return ESP_OK;
}
return ESP_FAIL;
}
#ifdef __cplusplus
}
#endif
#endif /* ! _ESP_HTTP_SERVER_H_ */

View File

@@ -1,373 +0,0 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <assert.h>
#include <esp_http_server.h>
#include <_esp_http_server.h>
#include "esp_httpd_priv.h"
#include "ctrl_sock.h"
static const char *TAG = "_httpd";
struct httpd_ctrl_data {
enum httpd_ctrl_msg {
HTTPD_CTRL_SHUTDOWN,
HTTPD_CTRL_WORK,
} hc_msg;
httpd_work_fn_t hc_work;
void *hc_work_arg;
};
static esp_err_t _httpd_server_init(struct httpd_data *hd)
{
int fd = socket(PF_INET6, SOCK_STREAM, 0);
if (fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
return ESP_FAIL;
}
struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
struct sockaddr_in6 serv_addr = {
.sin6_family = PF_INET6,
.sin6_addr = inaddr_any,
.sin6_port = htons(hd->config.server_port)
};
/* Enable SO_REUSEADDR to allow binding to the same
* address and port when restarting the server */
int enable = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
/* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
* it does not affect the normal working of the HTTP Server */
ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);
}
int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
close(fd);
return ESP_FAIL;
}
ret = listen(fd, hd->config.backlog_conn);
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
if (ctrl_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (msg_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
close(fd);
close(ctrl_fd);
return ESP_FAIL;
}
hd->listen_fd = fd;
hd->ctrl_fd = ctrl_fd;
hd->msg_fd = msg_fd;
return ESP_OK;
}
static void _httpd_process_ctrl_msg(struct httpd_data *hd)
{
struct httpd_ctrl_data msg;
int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
if (ret <= 0) {
ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
return;
}
if (ret != sizeof(msg)) {
ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
return;
}
switch (msg.hc_msg) {
case HTTPD_CTRL_WORK:
if (msg.hc_work) {
ESP_LOGD(TAG, LOG_FMT("work"));
(*msg.hc_work)(msg.hc_work_arg);
}
break;
case HTTPD_CTRL_SHUTDOWN:
ESP_LOGD(TAG, LOG_FMT("shutdown"));
hd->hd_td.status = THREAD_STOPPING;
break;
default:
break;
}
}
static esp_err_t _httpd_accept_conn(struct httpd_data *hd, int listen_fd)
{
/* If no space is available for new session, close the least recently used one */
if (hd->config.lru_purge_enable == true) {
if (!httpd_is_sess_available(hd)) {
/* Queue asynchronous closure of the least recently used session */
return httpd_sess_close_lru(hd);
/* Returning from this allowes the main server thread to process
* the queued asynchronous control message for closing LRU session.
* Since connection request hasn't been addressed yet using accept()
* therefore _httpd_accept_conn() will be called again, but this time
* with space available for one session
*/
}
}
struct sockaddr_in addr_from;
socklen_t addr_from_len = sizeof(addr_from);
int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
if (new_fd < 0) {
ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
struct timeval tv;
/* Set recv timeout of this fd as per config */
tv.tv_sec = hd->config.recv_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
/* Set send timeout of this fd as per config */
tv.tv_sec = hd->config.send_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
if (ESP_OK != httpd_sess_new(hd, new_fd)) {
ESP_LOGW(TAG, LOG_FMT("session creation failed"));
close(new_fd);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("complete"));
return ESP_OK;
}
/* Manage in-coming connection or data requests */
static esp_err_t _httpd_server(struct httpd_data *hd)
{
fd_set read_set;
FD_ZERO(&read_set);
if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
/* Only listen for new connections if server has capacity to
* handle more (or when LRU purge is enabled, in which case
* older connections will be closed) */
FD_SET(hd->listen_fd, &read_set);
}
FD_SET(hd->ctrl_fd, &read_set);
int tmp_max_fd;
httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
int maxfd = MAX(hd->listen_fd, tmp_max_fd);
tmp_max_fd = maxfd;
maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
if (active_cnt < 0) {
ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
httpd_sess_delete_invalid(hd);
return ESP_OK;
}
/* Case0: Do we have a control message? */
if (FD_ISSET(hd->ctrl_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
_httpd_process_ctrl_msg(hd);
if (hd->hd_td.status == THREAD_STOPPING) {
ESP_LOGD(TAG, LOG_FMT("stopping thread"));
return ESP_FAIL;
}
}
/* Case1: Do we have any activity on the current data
* sessions? */
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
if (httpd_sess_process(hd, fd) != ESP_OK) {
ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
close(fd);
/* Delete session and update fd to that
* preceding the one being deleted */
fd = httpd_sess_delete(hd, fd);
}
}
}
/* Case2: Do we have any incoming connection requests to
* process? */
if (FD_ISSET(hd->listen_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
if (_httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
}
}
return ESP_OK;
}
static void _httpd_close_all_sessions(struct httpd_data *hd)
{
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
httpd_sess_delete(hd, fd);
close(fd);
}
}
/* The main HTTPD thread */
static void _httpd_thread(void *arg)
{
int ret;
struct httpd_data *hd = (struct httpd_data *) arg;
hd->hd_td.status = THREAD_RUNNING;
ESP_LOGD(TAG, LOG_FMT("web server started"));
while (1) {
ret = _httpd_server(hd);
if (ret != ESP_OK) {
break;
}
}
ESP_LOGD(TAG, LOG_FMT("web server exiting"));
close(hd->msg_fd);
cs_free_ctrl_sock(hd->ctrl_fd);
_httpd_close_all_sessions(hd);
close(hd->listen_fd);
hd->hd_td.status = THREAD_STOPPED;
httpd_os_thread_delete();
}
static struct httpd_data *__httpd_create(const httpd_config_t *config)
{
/* Allocate memory for httpd instance data */
struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
if (!hd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
return NULL;
}
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
if (!hd->hd_calls) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
free(hd);
return NULL;
}
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
if (!hd->hd_sd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
free(hd->hd_calls);
free(hd);
return NULL;
}
struct httpd_req_aux *ra = &hd->hd_req_aux;
ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
if (!ra->resp_hdrs) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
if (!hd->err_handler_fns) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
free(ra->resp_hdrs);
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
/* Save the configuration for this instance */
hd->config = *config;
return hd;
}
static void _httpd_delete(struct httpd_data *hd)
{
struct httpd_req_aux *ra = &hd->hd_req_aux;
/* Free memory of httpd instance data */
free(hd->err_handler_fns);
free(ra->resp_hdrs);
free(hd->hd_sd);
/* Free registered URI handlers */
httpd_unregister_all_uri_handlers(hd);
free(hd->hd_calls);
free(hd);
}
esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
{
if (handle == NULL || config == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Sanity check about whether LWIP is configured for providing the
* maximum number of open sockets sufficient for the server. Though,
* this check doesn't guarantee that many sockets will actually be
* available at runtime as other processes may use up some sockets.
* Note that server also uses 3 sockets for its internal use :
* 1) listening for new TCP connections
* 2) for sending control messages over UDP
* 3) for receiving control messages over UDP
* So the total number of required sockets is max_open_sockets + 3
*/
if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
"Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
CONFIG_LWIP_MAX_SOCKETS - 3);
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = __httpd_create(config);
if (hd == NULL) {
/* Failed to allocate memory */
return ESP_ERR_HTTPD_ALLOC_MEM;
}
if (_httpd_server_init(hd) != ESP_OK) {
_httpd_delete(hd);
return ESP_FAIL;
}
httpd_sess_init(hd);
if (__httpd_os_thread_create_static(&hd->hd_td.handle, "httpd",
hd->config.stack_size,
hd->config.task_priority,
_httpd_thread, hd,
hd->config.core_id) != ESP_OK) {
/* Failed to launch task */
_httpd_delete(hd);
return ESP_ERR_HTTPD_TASK;
}
*handle = (httpd_handle_t *)hd;
return ESP_OK;
}

View File

@@ -353,6 +353,8 @@ static esp_err_t set_content_type_from_req(httpd_req_t *req)
return ESP_FAIL;
}
set_content_type_from_file(req, filename);
// we might have to disallow keep-alive in the future
// httpd_resp_set_hdr(req, "Connection", "close");
return ESP_OK;
}
@@ -508,7 +510,8 @@ esp_err_t console_cmd_post_handler(httpd_req_t *req){
}
else{
// navigate to the first child of the config structure
if(run_command(cJSON_GetStringValue(item))!=ESP_OK){
char *cmd = cJSON_GetStringValue(item);
if(!console_push(cmd, strlen(cmd) + 1)){
httpd_resp_send(req, (const char *)failed, strlen(failed));
}
else {

View File

@@ -22,7 +22,6 @@
#include "http_server_handlers.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "_esp_http_server.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
@@ -135,14 +134,16 @@ esp_err_t http_server_start()
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 25;
config.max_open_sockets = 8;
config.max_open_sockets = 3;
config.lru_purge_enable = true;
config.backlog_conn = 1;
config.uri_match_fn = httpd_uri_match_wildcard;
config.task_priority = ESP_TASK_PRIO_MIN;
//todo: use the endpoint below to configure session token?
// config.open_fn
ESP_LOGD(TAG, "Starting HTTP Server");
esp_err_t err= __httpd_start(&_server, &config);
esp_err_t err= httpd_start(&_server, &config);
if(err != ESP_OK){
ESP_LOGE_LOC(TAG,"Start server failed");
}

View File

@@ -88,6 +88,9 @@ menu "Squeezelite-ESP32"
string
default "bck=33,ws=25,do=15" if SQUEEZEAMP
default ""
config GPIO_EXP_CONFIG
string
default ""
config SPI_CONFIG
string
default "dc=27,data=19,clk=18" if TWATCH2020
@@ -333,6 +336,11 @@ menu "Squeezelite-ESP32"
help
Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]
config GPIO_EXP_CONFIG
string "GPIO expander configuration"
help
Set parameters of GPIO extender
model=<model>[,addr=<addr>][,base=<100..N>][,count=<0..32>][,intr=<gpio>][,port=dac|system]
endmenu
menu "LED configuration"
visible if !SQUEEZEAMP && !TWATCH2020

View File

@@ -263,18 +263,37 @@ void register_default_nvs(){
esp_read_mac((uint8_t *)&mac, ESP_MAC_WIFI_STA);
snprintf(macStr, LOCAL_MAC_SIZE-1,"-%x%x%x", mac[3], mac[4], mac[5]);
DEFAULT_NAME_WITH_MAC(default_bt_name,CONFIG_BT_NAME);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_name", default_bt_name);
config_set_default(NVS_TYPE_STR, "bt_name", default_bt_name, 0);
DEFAULT_NAME_WITH_MAC(default_host_name,DEFAULT_HOST_NAME);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "host_name", default_host_name);
config_set_default(NVS_TYPE_STR, "host_name", default_host_name, 0);
#if CONFIG_BT_SINK
DEFAULT_NAME_WITH_MAC(default_bt_name,CONFIG_BT_NAME);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_name", default_bt_name);
config_set_default(NVS_TYPE_STR, "bt_name", default_bt_name, 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "enable_bt_sink", STR(CONFIG_BT_SINK));
config_set_default(NVS_TYPE_STR, "enable_bt_sink", STR(CONFIG_BT_SINK), 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_sink_pin", STR(CONFIG_BT_SINK_PIN));
config_set_default(NVS_TYPE_STR, "bt_sink_pin", STR(CONFIG_BT_SINK_PIN), 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_sink_volume", "127");
config_set_default(NVS_TYPE_STR, "bt_sink_volume", "127", 0);
#endif
#if CONFIG_AIRPLAY_SINK
DEFAULT_NAME_WITH_MAC(default_airplay_name,CONFIG_AIRPLAY_NAME);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "airplay_name",default_airplay_name);
config_set_default(NVS_TYPE_STR, "airplay_name",default_airplay_name , 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "airplay_port", CONFIG_AIRPLAY_PORT);
config_set_default(NVS_TYPE_STR, "airplay_port", CONFIG_AIRPLAY_PORT, 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "enable_airplay", STR(CONFIG_AIRPLAY_SINK));
config_set_default(NVS_TYPE_STR, "enable_airplay", STR(CONFIG_AIRPLAY_SINK), 0);
#endif
DEFAULT_NAME_WITH_MAC(default_ap_name,CONFIG_DEFAULT_AP_SSID);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "ap_ssid", default_ap_name);
@@ -317,9 +336,6 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "ap_pwd", CONFIG_DEFAULT_AP_PASSWORD);
config_set_default(NVS_TYPE_STR, "ap_pwd", CONFIG_DEFAULT_AP_PASSWORD, 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "airplay_port", CONFIG_AIRPLAY_PORT);
config_set_default(NVS_TYPE_STR, "airplay_port", CONFIG_AIRPLAY_PORT, 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "a2dp_dev_name", CONFIG_A2DP_DEV_NAME);
config_set_default(NVS_TYPE_STR, "a2dp_dev_name", CONFIG_A2DP_DEV_NAME, 0);
@@ -351,18 +367,6 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "ota_prio", number_buffer);
config_set_default(NVS_TYPE_STR, "ota_prio", number_buffer, 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "enable_bt_sink", STR(CONFIG_BT_SINK));
config_set_default(NVS_TYPE_STR, "enable_bt_sink", STR(CONFIG_BT_SINK), 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_sink_pin", STR(CONFIG_BT_SINK_PIN));
config_set_default(NVS_TYPE_STR, "bt_sink_pin", STR(CONFIG_BT_SINK_PIN), 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bt_sink_volume", "127");
config_set_default(NVS_TYPE_STR, "bt_sink_volume", "127", 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "enable_airplay", STR(CONFIG_AIRPLAY_SINK));
config_set_default(NVS_TYPE_STR, "enable_airplay", STR(CONFIG_AIRPLAY_SINK), 0);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "display_config", CONFIG_DISPLAY_CONFIG);
config_set_default(NVS_TYPE_STR, "display_config", CONFIG_DISPLAY_CONFIG, 0);
@@ -386,12 +390,15 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s", "dac_config");
config_set_default(NVS_TYPE_STR, "dac_config", "", 0);
//todo: add dac_config for known targets
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "jack_mutes_amp");
config_set_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "gpio_exp_config");
config_set_default(NVS_TYPE_STR, "gpio_exp_config", CONFIG_GPIO_EXP_CONFIG, 0);
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);
@@ -425,6 +432,7 @@ void handle_ap_connect(){
start_telnet(NULL);
halSTORAGE_RebootCounterUpdate(0);
}
void app_main()
{
const esp_partition_t *running = esp_ota_get_running_partition();

View File

@@ -16,7 +16,6 @@
#define CONFIG_SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"
#endif
extern void run_command(char * line);
extern bool wait_for_wifi();
extern void console_start();
extern pthread_cond_t wifi_connect_suspend_cond;