Compare commits

...

160 Commits

Author SHA1 Message Date
github-actions
0a653bf374 Update prebuilt objects [skip actions] 2023-10-19 00:09:58 +00:00
philippe44
2a77d09f11 fix Spotify queue modifications - release 2023-10-18 17:07:29 -07:00
philippe44
d03678ea81 misc changes (see PR) (#340)
* misc changes (see PR)

* macro only parsing
2023-10-16 21:50:50 -04:00
github-actions
5019b5bf0f Update prebuilt objects [skip actions] 2023-10-13 16:21:39 +00:00
Sebastien L
3f1a7265b1 Use DEFINES for parsing - release
fixes https://github.com/sle118/squeezelite-esp32/issues/332
2023-10-13 12:19:21 -04:00
philippe44
338eea33d1 Update README.md 2023-10-11 15:43:03 -07:00
philippe44
ffb79e1e8c Update README.md 2023-10-11 15:36:21 -07:00
github-actions
1fe515b18d Update prebuilt objects [skip actions] 2023-10-11 18:46:48 +00:00
Sébastien
fa5b2c8e45 Update CHANGELOG [skip actions] 2023-10-11 14:39:04 -04:00
Sebastien L
9b97404fa2 Change UI to allow disabling squeezelite 2023-10-11 14:34:44 -04:00
Sébastien
a0d3c60f62 Update CHANGELOG [skip actions] 2023-10-11 12:42:02 -04:00
Sébastien
b60aed659a Update CHANGELOG [skip actions] 2023-10-11 12:41:10 -04:00
Sebastien L
484d8c54a8 Trim app and recovery binaries 2023-10-11 12:36:17 -04:00
philippe44
9ebe717e74 add delay and gpio on dac_controlset 2023-10-10 13:51:47 -07:00
philippe44
12347bdb29 Update README.md 2023-10-09 14:38:06 -07:00
philippe44
a42c9ff860 Update README.md 2023-10-09 14:35:54 -07:00
philippe44
e67413697c Update README.md 2023-10-09 14:35:01 -07:00
philippe44
c4b797e54f update defaults 2023-10-09 13:45:46 -07:00
philippe44
9971fb0ff3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-08 17:15:20 -07:00
philippe44
8280bc4903 catchup with cspot "official" - release 2023-10-08 17:15:14 -07:00
github-actions
adc6a86725 Update prebuilt objects [skip actions] 2023-10-07 06:40:24 +00:00
philippe44
61f58f9a52 cspot fixes - release 2023-10-06 23:38:07 -07:00
github-actions
e3650413f5 Update prebuilt objects [skip actions] 2023-10-06 18:24:24 +00:00
Sebastien L
750ffbf464 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-06 14:19:52 -04:00
Sebastien L
65f52a23bc fix manual workflow triggering [skip actions] 2023-10-06 14:19:47 -04:00
Sébastien
da411bf1c8 Update CHANGELOG [skip actions] 2023-10-06 14:07:28 -04:00
Sebastien L
0a319269c2 resolve bootswatch contrast issue [skip actions]
This will solve https://github.com/sle118/squeezelite-esp32/issues/319
2023-10-06 14:05:49 -04:00
Sebastien L
b0ce38bf14 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-10-06 11:50:02 -04:00
Sebastien L
767b677947 Update build system [skip actions] 2023-10-06 11:49:58 -04:00
github-actions
724818390b Update prebuilt objects [skip actions] 2023-10-04 22:54:15 +00:00
philippe44
105e800cc1 fix podcast on Spotify - release 2023-10-04 15:51:55 -07:00
github-actions
ab09f009f7 Update prebuilt objects [skip actions] 2023-10-03 03:09:28 +00:00
Sébastien
9c179adf85 Update CHANGELOG release 2023-10-02 23:07:00 -04:00
philippe44
719b289659 update cspot 2023-10-02 19:06:59 -07:00
Sébastien
eb7df4a5e9 attempt at fixing build parameter logic. [skip actions] 2023-10-02 07:50:39 -04:00
Sebastien L
9a54239323 UI build should be manually triggered 2023-10-01 21:37:25 -04:00
Sébastien
b790156be0 Update CHANGELOG 2023-10-01 16:48:27 -04:00
Sébastien
f87b3adec2 Update CHANGELOG-release 2023-10-01 16:38:05 -04:00
Sebastien L
35d42f2096 roll back webapp 2023-10-01 16:23:01 -04:00
Sébastien
d2d0cadeed Make UI build optional 2023-09-30 23:33:04 -04:00
github-actions
cd76619e96 Update prebuilt objects [skip actions] 2023-09-30 01:41:41 +00:00
philippe44
781699362f add CHANGELOG (at last) - release 2023-09-29 18:37:47 -07:00
philippe44
c4dbf60cb6 Update README.md 2023-09-28 17:23:06 -07:00
philippe44
3dd7b4b02c Update README.md 2023-09-28 17:19:35 -07:00
philippe44
83c1a2b8e0 Update README.md 2023-09-28 17:18:59 -07:00
philippe44
e8021c38f0 Update README.md 2023-09-28 17:17:58 -07:00
philippe44
71bb57f1eb Update README.md 2023-09-28 17:16:40 -07:00
philippe44
7eb4b218e3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-28 17:11:05 -07:00
philippe44
953d1657f9 add soft on/off schematics 2023-09-28 17:09:56 -07:00
philippe44
b21a143196 Delete 269289674-4d8eb4f6-0d88-4dab-858f-c3de3ec33139.png 2023-09-28 16:54:50 -07:00
philippe44
898f1d92ed Add files via upload 2023-09-28 16:53:13 -07:00
philippe44
f6fd11783c Merge pull request #313 from wizmo2/rmt-esp32s3
Support for ESP32S3 RMT channel restrictions
2023-09-28 10:33:11 -07:00
Wizmo2
f33cb569ce rmt helper to function 2023-09-28 07:34:58 -04:00
philippe44
f5d6f26c01 Update README.md 2023-09-27 19:42:02 -07:00
philippe44
8df6b853e6 Merge pull request #312 from wizmo2/st7789-mod
Allow offset on ST7789
2023-09-27 19:41:21 -07:00
philippe44
f7c0107d40 Merge pull request #314 from wizmo2/led_vu_status
Add battery status to led_vu
2023-09-27 19:40:43 -07:00
philippe44
5068309d25 manage Spotify credentials 2023-09-27 19:36:38 -07:00
philippe44
506a5aaf7a Update README.md 2023-09-27 19:22:50 -07:00
philippe44
fe409730e0 Update README.md 2023-09-27 18:13:31 -07:00
Wizmo2
4d6cfaca1c format changes 2023-09-27 06:25:30 -04:00
Wizmo2
69e61ce451 add battery status to vu meter effect 2023-09-25 20:57:37 -04:00
Wizmo2
60bd591bf8 support for c3 and s3 channel restrictions 2023-09-25 19:32:13 -04:00
Wizmo2
3ea26a0c6f allow offset on ST7789 2023-09-25 17:47:26 -04:00
philippe44
53fa83b2dd cspot dsconnect when paused handling 2023-09-24 00:18:30 -07:00
philippe44
90f53db953 http_download needs a bigger stack for cspot which required to move it to EXTRAM 2023-09-23 22:20:01 -07:00
philippe44
b413780048 some small speed further optimization to 24 bits SPDIF 2023-09-22 22:38:37 -07:00
philippe44
456f16fc79 Update README.md 2023-09-22 22:32:05 -07:00
philippe44
04e2917351 Update README.md 2023-09-22 22:29:58 -07:00
philippe44
dc9e1191a2 move sleep init at the very end of boot (all must be initialized) 2023-09-22 15:27:27 -07:00
philippe44
72a8fb2249 Merge pull request #307 from UrbanLienert/spdif-24bit
24bit SPDIF output
2023-09-22 15:26:29 -07:00
UrbanLienert
f409a9ee28 made vucp static 2023-09-22 08:06:23 +02:00
UrbanLienert
b85cf98cdf rmoved duplicated lines 2023-09-21 16:24:20 +02:00
UrbanLienert
e85c967220 changed 16bit as well 2023-09-20 18:51:13 +02:00
philippe44
ce21ff1b76 Update README.md 2023-09-18 22:34:32 -07:00
philippe44
7c71fb6a65 Update README.md 2023-09-18 18:40:30 -07:00
philippe44
bb22d4ea02 Update README.md 2023-09-18 18:39:37 -07:00
philippe44
e0e749fb5b add (some) spurious IR detection capability 2023-09-18 18:34:03 -07:00
UrbanLienert
c650bc7658 new version fix 2023-09-18 22:39:10 +02:00
philippe44
a76ce3f344 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-18 11:40:05 -07:00
philippe44
1d059be001 clz for 64 bits requies special version( wake bitmap) 2023-09-18 11:40:01 -07:00
UrbanLienert
32195b50ba 24bit spdif output 2023-09-18 17:13:58 +02:00
philippe44
a7539a5332 Update README.md 2023-09-17 20:38:44 -07:00
philippe44
450ebae399 Update README.md 2023-09-17 20:37:36 -07:00
philippe44
d238063c49 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-17 20:00:15 -07:00
philippe44
58f2e4488b don't count failed IR as activity 2023-09-17 20:00:11 -07:00
philippe44
b83e2722c0 Update README.md 2023-09-16 23:17:19 -07:00
philippe44
96a3f8aab0 Update README.md 2023-09-16 19:48:17 -07:00
philippe44
66b88d186a fix some buttons snafu & power off led on suspend 2023-09-16 19:42:31 -07:00
philippe44
bb185d76dc add buttons/rotary/ir/BT sink in sleep + battery low-voltage 2023-09-16 17:55:41 -07:00
philippe44
f4c0a91e84 Merge pull request #303 from wizmo2/fix_led_vu
Fix led_vu
2023-09-16 17:24:46 -07:00
Wizmo2
2ecf2e6098 hacked led_strip 2023-09-16 17:13:07 -04:00
Wizmo2
f32e4de84b static declaration in function 2023-09-16 16:47:40 -04:00
Wizmo2
55303ec1b3 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-16 16:46:27 -04:00
philippe44
effc574e50 Update README.md 2023-09-16 00:35:09 -07:00
philippe44
3941a26b67 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-16 00:24:49 -07:00
philippe44
b47074e668 Update README.md 2023-09-15 21:32:46 -07:00
philippe44
822de92df1 Update README.md 2023-09-15 21:31:54 -07:00
philippe44
6bd778c7c6 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-15 21:26:36 -07:00
philippe44
28ecad4652 ACTRLS_SLEEP should be last (indexed array) and red led is not default for parsing 2023-09-15 21:26:33 -07:00
philippe44
ddd6bddde7 Update README.md 2023-09-15 20:46:41 -07:00
philippe44
f4e899fc60 add option to maintain some RTC GPIO pull-up/down 2023-09-15 20:38:47 -07:00
philippe44
c61ff05081 take into account case with no wake option 2023-09-15 18:50:26 -07:00
philippe44
c4df0c93f9 Update README.md 2023-09-15 18:47:23 -07:00
philippe44
fae09ba29e Update README.md 2023-09-15 18:43:02 -07:00
Wizmo2
fd0c38c49f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into fix_led_vu 2023-09-15 20:28:18 -04:00
philippe44
a83f14113e add sleep option + potentially fix led_vu issue 2023-09-15 17:18:06 -07:00
Wizmo2
7f0b411dac fix endings 2023-09-15 20:15:53 -04:00
Wizmo2
3350a8dbc7 restore static struct for led_vu 2023-09-15 20:08:31 -04:00
philippe44
a2eddb5411 Update README.md 2023-09-15 16:50:20 -07:00
philippe44
65ff5f7c2a Update README.md 2023-09-14 19:26:53 -07:00
philippe44
5ed2f6d03e Update README.md 2023-09-14 19:23:56 -07:00
philippe44
d8bd588982 Update README.md 2023-09-14 19:21:30 -07:00
philippe44
e4481a95f9 Update README.md 2023-09-14 19:21:17 -07:00
github-actions
992f5c361c Update prebuilt objects [skip actions] 2023-09-13 23:03:19 +00:00
philippe44
08c7ccdf28 put timestamp at end of ellipse 2023-09-12 18:37:27 -07:00
philippe44
0002256630 Fix AirPlay artwork disappearing 2023-09-12 18:16:38 -07:00
philippe44
b3ee25e3be add "power" gpio 2023-09-12 15:03:58 -07:00
philippe44
d53ae66547 Update README.md 2023-09-12 15:03:28 -07:00
Sébastien
26708ea51a Update bug_report.md 2023-09-12 12:43:12 -04:00
Sébastien
9c711d73f5 Update issue templates 2023-09-12 12:32:20 -04:00
github-actions
d0ac871a3b Update prebuilt objects [skip actions] 2023-09-12 01:39:04 +00:00
philippe44
38fee20680 better handling of AIrPlay network issues - release 2023-09-11 18:36:08 -07:00
philippe44
12ae3d08b6 Update README.md 2023-09-11 13:28:06 -07:00
github-actions
2931a5100e Update prebuilt objects [skip actions] 2023-09-11 05:32:43 +00:00
philippe44
c148cd16ae Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-09-10 22:27:41 -07:00
philippe44
38fb90d179 pseudo-idle task requires more stack (cJSON) - release 2023-09-10 22:27:36 -07:00
github-actions
61ff6a8915 Update prebuilt objects [skip actions] 2023-09-10 05:15:25 +00:00
philippe44
add6ff37ba Merge pull request #295 from urknall/master-v4.3
typo
2023-09-09 15:58:49 -07:00
philippe44
ae2ad85dec fix another ancient bug with sync start time (visible with AirPlay and SPDIF, but impacts all) 2023-09-09 15:57:58 -07:00
philippe44
a72f471c35 ancient AirPlay bug : passing thread context in mutex destroy 2023-09-08 22:35:06 -07:00
urknall
f1108332d9 typo 2023-09-08 21:32:48 +02:00
philippe44
d10d12a85b ES8388 i2c addresses 2023-09-05 17:11:16 -07:00
philippe44
956392ebfd remove i2s.c0 2023-09-05 11:51:36 -07:00
philippe44
f4ddc450a1 Update README.md 2023-09-04 18:35:13 -07:00
philippe44
926c567345 full i2s fix & loudness limited log 2023-09-04 16:06:49 -07:00
philippe44
0b077b5234 fix spdif+spotify stuttering and refactor stats => add pseudo_idle chained callbacks 2023-09-03 17:34:45 -07:00
philippe44
45cae64c83 Update README.md 2023-09-03 17:31:04 -07:00
philippe44
7c1a1081c4 as equalizer parameter change produced a small glitch, do not update these if volume has not changes 2023-09-01 21:44:54 -07:00
philippe44
4227fc9603 Update README.md 2023-09-01 21:33:07 -07:00
philippe44
c7e4d9711c add addressable led on led API including green/red + make rmt properly shared accross services 2023-09-01 21:22:42 -07:00
philippe44
e09837158c finalize esp32 vs esp32-s3 single sourceset 2023-08-31 21:26:27 -07:00
philippe44
067a1f2800 proposed way to handle loudness 0..10 2023-08-31 18:00:58 -07:00
philippe44
ad4d5db2f1 make some room in recovery 2023-08-30 23:47:25 -07:00
philippe44
55053d5941 fix spdif for s3 and remove one override
SPDIF on esp32 was partly incorrect due to word ordering and required i2s_hal override. This is not needed anymore as the "mistery" of SPDIF hack has been properly sorted out
2023-08-30 22:00:05 -07:00
philippe44
e9ccd8eef7 log consistency 2023-08-29 16:49:18 -07:00
philippe44
aa8554b722 remove some un-needed logs 2023-08-29 16:46:30 -07:00
philippe44
fd502a01d7 more esp32-s3 compatibility 2023-08-29 12:23:13 -07:00
philippe44
948a02efee Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-29 12:09:27 -07:00
philippe44
d1858c3cc3 make it compatible with idf 4.4 2023-08-29 12:09:15 -07:00
philippe44
eaa35b3677 Update README.md 2023-08-29 11:45:49 -07:00
philippe44
9f170020e2 fix plugin loudness value being memorized upon tab change 2023-08-28 20:47:52 -07:00
philippe44
809b55579f add plugin and fix loudness = 0 disable equalizer 2023-08-28 18:59:17 -07:00
philippe44
85a3bf8836 add loudness (alpha) 2023-08-28 17:51:50 -07:00
philippe44
3df589d7ab Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-27 15:10:05 -07:00
philippe44
118462f5d7 logs of what IR cannot decode 2023-08-27 15:10:00 -07:00
wizmo2
ca7670f754 fix DAC config to add I2S (either 'I2S' or blank) (#293)
Co-authored-by: Wizmo2 <wizmo.home@tahoo.com>
2023-08-27 16:02:57 -04:00
philippe44
b154f60e8e Update README.md 2023-08-26 19:49:31 -07:00
philippe44
e2b13a8a3f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-08-26 19:43:04 -07:00
philippe44
da87c859d0 add RC5 (maybe) 2023-08-26 19:42:32 -07:00
github-actions
3a424d0d58 Update prebuilt objects [skip actions] 2023-08-27 01:00:24 +00:00
196 changed files with 7899 additions and 3883 deletions

90
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,90 @@
---
name: Bug report
about: How to Submit an Issue for the squeezelite-esp32 Project
title: ''
labels: ''
assignees: ''
---
To help us resolve your issue as quickly as possible, please follow these guidelines when submitting an issue. Providing all the necessary information will save both your time and ours.
### Describe the bug
A clear and concise description of what the bug is.
### Preliminary Information
1. **Firmware Version**: Specify the version of the firmware you are using.
2. **Plugin Version**: Mention the version of the plugin installed on your LMS (Logitech Media Server).
### Hardware Details
Please describe your hardware setup:
- **ESP32 Module**: For example, ESP32 WROVER, ESP32-S3, etc.
- **Board Type**: If applicable, e.g., ESP32 audio kit, etc.
- **DAC Chip**: Specify the DAC chip you are using.
- **Additional Hardware**: Include details about any other hardware like rotary controls, buttons, screens (SPI, I2C), Ethernet, IO expansion, etc.
### NVS Settings
Follow these steps to share your NVS settings:
1. Open the web UI of your device.
2. Click on the "Credit" tab.
3. Enable the "Show NVS Editor" checkbox. This allows you to view or change the NVS configuration even when not in recovery mode.
4. Navigate to the "NVS Editor" tab.
5. Scroll to the bottom and click "Download Config".
6. Share the downloaded content here.
<details>
<pre><code>
Your log content here
</code></pre>
</details>
### Logs
To share logs:
1. Connect your player to a computer using a USB cable. Use a built-in Serial-USB adapter if your player has one, or an external USB adapter otherwise.
2. Go to the [web installer](https://sle118.github.io/squeezelite-esp32-installer/).
3. Click "Connect to Device".
4. Select the appropriate serial port.
5. Click "Logs And Console".
6. Download the logs and share them here. Please remove any sensitive information like Wi-Fi passwords or MAC addresses.
- **If the problem occurs soon after booting**: Share the full log until the issue occurs.
- **If the problem occurs later during playback**: Trim the logs to include information just before and after the problem occurs.
#### Example Log
Here's an example log for reference. Make sure to obfuscate sensitive information like Wi-Fi passwords, MAC addresses, and change IP addresses to something more generic.
<details>
<pre><code>
=== START OF LOG ===
Example of log from the console
rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
...
I (1041) cpu_start: Application information:
I (1044) cpu_start: Project name: Squeezelite-ESP32
I (1050) cpu_start: App version: I2S-4MFlash-1336
I (1055) cpu_start: Compile time: Aug 12 2023 01:20:18
I (1062) cpu_start: ELF file SHA256: 34241d6e99fd1d6b...
I (1068) cpu_start: ESP-IDF: v4.3.5-dirty
...
I (1133) heap_init: At 40094A8C len 0000B574 (45 KiB): IRAM
I (1139) spiram: Adding pool of 4066K of external SPI memory to heap allocator
=== END OF LOG ===
</code></pre>
</details>
### Issue Description
1. **Observed Behavior**: Describe what you think is wrong.
2. **Expected Behavior**: Describe what you expect should happen.
3. **Steps to Reproduce**: Provide a step-by-step guide on how to replicate the issue.

View File

@@ -34,14 +34,15 @@ jobs:
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Set build flags
id: build_flags
run: |
git config --global --add safe.directory /__w/squeezelite-esp32/squeezelite-esp32
[ ${{github.event.inputs.ui_build}} ] && ui_build_option="--ui_build" || ui_build_option=""
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> $GITHUB_ENV
echo "release_build_option=$release_build_option" >> $GITHUB_ENV
[ ${{github.event.inputs.release_build}} ] && release_build_option="--force" || release_build_option=""
echo "ui_build_option=$ui_build_option" >> "$GITHUB_OUTPUT"
echo "release_build_option=$release_build_option" >> "$GITHUB_OUTPUT"
echo "Dumping environment"
env
. /opt/esp/python_env/idf4.3_py3.8_env/bin/activate
@@ -49,7 +50,7 @@ jobs:
# --mock - to mock the compilation part - this is to be used for testing only
# --force - to force a release build even if the last commit message doesn't contain the word "release"
# --ui_build - to force a ui_build even if the last commit message doesn't contain "[ui-build]"
build_tools.py build_flags $ui_build_option $release_build_option
build_tools.py build_flags $ui_build_option $release_build_option
- name: Show Build Flags
run: |
echo "Running with the following options"
@@ -196,7 +197,7 @@ jobs:
release_name: ${{ env.name }}
body: ${{ env.description }}
draft: false
prerelease: true
prerelease: false
- name: Upload Release Asset - Squeezelite binary file
if: ${{ needs.bootstrap.outputs.release_flag == 1 && needs.bootstrap.outputs.mock == 0 }}
id: upload-release-asset

3
.gitmodules vendored
View File

@@ -8,3 +8,6 @@
[submodule "components/wifi-manager/UML-State-Machine-in-C"]
path = components/wifi-manager/UML-State-Machine-in-C
url = https://github.com/kiishor/UML-State-Machine-in-C
[submodule "components/wifi-manager/webapp/src/bootswatch"]
path = components/wifi-manager/webapp/src/bootswatch
url = https://github.com/thomaspark/bootswatch.git

27
CHANGELOG Normal file
View File

@@ -0,0 +1,27 @@
2023-10-11
- Reduce the size of binaries (Fixes https://github.com/sle118/squeezelite-esp32/issues/329)
- [WEB] Allow running without LMS with option "Audio/Disable Squeezelite"
2023-10.07
- catchup with official cspot
2023-10-06
- fix cspot PREV on first track, NEXT on last track and normal ending
- use DMA_AUTO for SPI
- cspot share same time log
2023-10-06
- Fix bootswatch bug that caused difficult to read UI ( issue #319)
2023-10-02
- update cspot
2023-09-29
- sleep mechanism
- spotify can store credentials so that zeroconf is optional and players are always registered
- add battery to led_vu (see credits)
- spdif can do 24 bits (see credits)
- rmt fixes
- airplay & spotify artwork fixes
- airplay stability improvments
- fix UI text color

118
README.md
View File

@@ -1,15 +1,16 @@
[![Platform Build](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml/badge.svg)](https://github.com/sle118/squeezelite-esp32/actions/workflows/Platform_build.yml)
# Squeezelite-esp32
## Forewords
**More and more people seems to use this without a LMS server, just for BT, AirPlay or Spotify. It's fine but understand that squeezeliteESP32 is primarily a Logitech Media Server player and has been designed around that concept. All the others are add-ons stitched to it, so other modes have their shortcomings. So please make sure you read [this](#Additional-configuration-notes-from-the-Web-UI) before opening an issue**
## What is this?
Squeezelite-esp32 is an audio software suite made to run on espressif's ESP32 wifi (b/g/n) and bluetooth chipset. It offers the following capabilities
Squeezelite-esp32 is an audio software suite made to run on espressif's esp32 and esp32-s3 wifi (b/g/n) and bluetooth chipsets. It offers the following capabilities
- Stream your local music and connect to all major on-line music providers (Spotify, Deezer, Tidal, Qobuz) using [Logitech Media Server - a.k.a LMS](https://forums.slimdevices.com/) and enjoy multi-room audio synchronization. LMS can be extended by numerous plugins and can be controlled using a Web browser or dedicated applications (iPhone, Android). It can also send audio to UPnP, Sonos, ChromeCast and AirPlay speakers/devices.
- Stream from a **Bluetooth** device (iPhone, Android)
- Stream from an **AirPlay** controller (iPhone, iTunes ...) and enjoy synchronization multiroom as well (although it's AirPlay 1 only)
- Stream direcly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)
- Stream directly from **Spotify** using SpotifyConnect (thanks to [cspot](https://github.com/feelfreelinux/cspot)) - please read carefully [this](#spotify)
Depending on the hardware connected to the ESP32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
Depending on the hardware connected to the esp32, you can send audio to a local DAC, to SPDIF or to a Bluetooth speaker. The bare minimum required hardware is a WROVER module with 4MB of Flash and 4MB of PSRAM (https://www.espressif.com/en/products/modules/esp32). With that module standalone, just apply power and you can stream to a Bluetooth speaker. You can also send audio to most I2S DAC as well as to SPDIF receivers using just a cable or an optical transducer.
But squeezelite-esp32 is highly extensible and you can add
@@ -17,12 +18,13 @@ But squeezelite-esp32 is highly extensible and you can add
- [GPIO expander](#gpio-expanders) (buttons, led and rotary)
- [IR receiver](#infrared) (no pullup resistor or capacitor needed, just the 38kHz receiver)
- [Monochrome, GrayScale or Color displays](#display) using SPI or I2C (supported drivers are SH1106, SSD1306, SSD1322, SSD1326/7, SSD1351, ST7735, ST7789 and ILI9341).
- [Ethernet](#ethernet-required-unpublished-version-43) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
- [LED strip](#led-strip) for VU-meter
- [Ethernet](#ethernet) using a Microchip LAN8720 with RMII interface or Davicom DM9051/W5500 over SPI.
Other features include
- Resampling
- 10-bands equalizer
- Resampling (16 bits mode)
- 10-bands equalizer (16 bits mode)
- Automatic initial setup using any WiFi device
- Full web interface for further configuration/management
- Firmware over-the-air update
@@ -53,6 +55,8 @@ In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommend
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
**IMPORTANT: on esp32 (not esp32-s3), using Spotify with SPDIF produces stuttering audio when "stats" are enabled. You MUST disable them**
## Supported Hardware
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
@@ -65,6 +69,9 @@ Please note that when sending to a Bluetooth speaker (source), only 44.1 kHz can
Most DAC will work out-of-the-box with simply an I2S connection, but some require specific commands to be sent using I2C. See DAC option below to understand how to send these dedicated commands. There is build-in support for TAS575x, TAS5780, TAS5713 and AC101 DAC.
### Raw WROOM esp32-s3 module
The esp32-s3 based modules like [this](https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf) are also supported but requires esp-idf 4.4. It is not yet part of official releases, but it compiles & runs. The s3 does not have bluetooth audio. Note that CPU performances are greatly enhanced.
### SqueezeAMP
This is the main hardware companion of Squeezelite-esp32 and has been developped together. Details on capabilities can be found [here](https://forums.slimdevices.com/showthread.php?110926-pre-ANNOUNCE-SqueezeAMP-and-SqueezeliteESP32) and [here](https://github.com/philippe44/SqueezeAMP).
@@ -76,6 +83,8 @@ NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hard
- dac_config: `model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0`
- spdif_config: `bck=33,ws=25,do=15`
The IR can be used as a wake-up signal using (setting `sleep_config` with `wake=0:0`). It's a pull-up so it stays at 1 when not receiving anything which means it cannot be used in conjuction with other wake-up IOs. See [Sleeping](#sleeping) for more details regarding the limitation of waking-up upon multiple inputs.
### MuseLuxe
This portable battery-powered [speaker](https://raspiaudio.com/produit/esp-muse-luxe) is compatible with squeezelite-esp32 for which there is a dedicated build supplied with every update. If you want to rebuild, use the `squeezelite-esp32-Muse-sdkconfig.defaults` configuration file.
@@ -119,7 +128,7 @@ or
- dac_config: `model=ES8388,bck=27,ws=25,do=26,sda=33,scl=32,i2c=16`
### T-WATCH2020 by LilyGo
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its dac (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1290&FId=t3:50036:3) based on ESP32. It has a 240x240 ST7789 screen and onboard audio. Not very useful to listen to anything but it works. This is an example of a device that requires an I2C set of commands for its DAC/APU (see below). There is a build-option if you decide to rebuild everything by yourself, otherwise the I2S default option works with the following parameters
- dac_config: `model=I2S,bck=26,ws=25,do=33,i2c=53,sda=21,scl=22`
- dac_controlset:
@@ -130,7 +139,7 @@ This is a fun [smartwatch](http://www.lilygo.cn/prod_view.aspx?TypeId=50036&Id=1
- display_config: `SPI,driver=ST7789,width=240,height=240,cs=5,back=12,speed=16000000,HFlip,VFlip`
### ESP32-WROVER + I2S DAC
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work others may also work. PCM5012 DACs can be hooked up via:
Squeezelite-esp32 requires esp32 chipset and 4MB PSRAM. ESP32-WROVER meets these requirements. To get an audio output an I2S DAC can be used. Cheap PCM5102 I2S DACs work but many others also do. PCM5012 DACs can be hooked up via:
I2S - WROVER
VCC - 3.3V
@@ -174,11 +183,11 @@ Default and only "host" is 1 as others are used already by flash and spiram. The
### DAC/I2S
The NVS parameter "dac_config" set the gpio used for i2s communication with your DAC. You can define the defaults at compile time but nvs parameter takes precedence except for named configurations
```
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
bck=<gpio>,ws=<gpio>,do=<gpio>[,mck=0|1|2][,mute=<gpio>[:0|1][,model=TAS57xx|TAS5713|AC101|WM8978|ES8388|I2S][,sda=<gpio>,scl=<gpio>[,i2c=<addr>]]
```
if "model" is not set or is not recognized, then default "I2S" is used. The option "mck" is used for some codecs that require a master clock (although they should not). By default GPIO0 is used as MCLK and only recent builds (post mid-2023) can use 1 or 2. Also be aware that this cannot coexit with RMII Ethernet (see ethernet section below). I2C parameters are optional and only needed if your DAC requires an I2C control (See 'dac_controlset' below). Note that "i2c" parameters are decimal, hex notation is not allowed.
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speakder and headset on and off using a JSON syntax:
So far, TAS57xx, TAS5713, AC101, WM8978 and ES8388 are recognized models where the proper init sequence/volume/power controls are sent. For other codecs that might require an I2C commands, please use the parameter "dac_controlset" that allows definition of simple commands to be sent over i2c for init, power, speaker and headset on and off using a JSON syntax:
```json
{ <command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
<command>: [ {"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"}, ... {{"reg":<register>,"val":<value>,"mode":<nothing>|"or"|"and"} ],
@@ -188,6 +197,8 @@ Where `<command>` is one of init, poweron, poweroff, speakeron, speakeroff, head
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. The 'val parameter can be an array [v1, v2,...] to write a serie of bytes in a single i2c burst (in that case 'mode' is ignored). **Note that all values must be decimal**. You can use a validator like [this](https://jsonlint.com) to verify your syntax
The 'power' command is used when powering on/off the DAC after the idle period (see -C option of squeezelite) and the 'speaker/headset' commands are sent when switching between speakers and headsets (see headset jack detection).
NB: For named configurations ((SqueezeAMP, Muse ... all except I2S), all this is ignored. For know codecs, the built-in sequences can be overwritten using dac_controlset
**Please note that you can not use the same GPIO or port as the I2C.**
@@ -205,7 +216,7 @@ bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For named configurations, this is ignored
To optimize speed, a bit-manipulation trick is used and as a result, the bit depth is limited to 20 bits, even in 32 bits mode. As said before, this is more than enough for any human ear. In theory, it could be extended up to 23 bits but I don't see the need. Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
The maximum bit depth is 24 bits, even in 32 bits mode (this a SPDIF limitation - thank @UrbanLienert for theupdate from 20 to 24 bit). Now, you can also get SPDIF using a specialized chip that offers a I2S interface like a DAC but spits out SPDIF (optical and coax). Refers to DAC chapter then.
If you want coax, you can also use a poor-man's trick to generate signal from a 3.3V GPIO. All that does is dividing the 3.3V to generate a 0.6V peak-to-peak and then remove DC
```
@@ -221,7 +232,7 @@ Ground -------------------------- coax signal ground
The NVS parameter "display_config" sets the parameters for an optional display. It can be I2C (see [here](#i2c) for shared bus) or SPI (see [here](#spi) for shared bus) Syntax is
```
I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,reset=<gpio>][,HFlip][,VFlip][driver=SSD1306|SSD1326[:1|4]|SSD1327|SH1106]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735[:x=<offset>][:y=<offset>]|ST7789|ILI9341[:16|18][,rotate]]
SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1322|SSD1326[:1|4]|SSD1327|SH1106|SSD1675|ST7735|ST7789[:x=<offset>][:y=<offset>]|ILI9341[:16|18][,rotate]]
```
- back: a LED backlight used by some older devices (ST7735). It is PWM controlled for brightness
- reset: some display have a reset pin that is should normally be pulled up if unused. Most displays require reset and will not initialize well otherwise.
@@ -251,7 +262,7 @@ The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay a
- 'artwork' enables coverart display, if available (does not work for Bluetooth). The optional parameter indicates if the artwork should be resized (1) to fit the available space. Note that the built-in resizer can only do 2,4 and 8 downsizing, so fit is not optimal. The artwork will be placed at the right of the display for landscape displays and underneath the two information lines for others (there is no user option to tweak that).
### Infrared
You can use any IR receiver compatible with NEC protocol (38KHz). Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
You can use any IR receiver compatible with NEC protocol (38KHz) or RC5. Vcc, GND and output are the only pins that need to be connected, no pullup, no filtering capacitor, it's a straight connection.
The IR codes are send "as is" to LMS, so only a Logitech SB remote from Boom, Classic or Touch will work. I think the file Slim_Devices_Remote.ir in the "server" directory of LMS can be modified to adapt to other codes, but I've not tried that.
@@ -266,16 +277,18 @@ GPIO can be set to GND provide or Vcc at boot. This is convenient to power devic
The `<amp>` parameter can use used to assign a GPIO that will be set to active level (default 1) when playback starts. It will be reset when squeezelite becomes idle. The idle timeout is set on the squeezelite command line through `-C <timeout>`
The `<power>` parameter can use used to assign a GPIO that will be set to active level (default 1) when player is powered on and reset when powered off (in LMS, does not apply to AirPlay, Spotify or BT).
If you have an audio jack that supports insertion (use :0 or :1 to set the level when inserted), you can specify which GPIO it's connected to. Using the parameter jack_mutes_amp allows to mute the amp when headset (e.g.) is inserted.
You can set the Green and Red status led as well with their respective active state (:0 or :1)
You can set the Green and Red status led as well with their respective active state (:0 or :1) or specific the chipset if you use addressable RGB led.
The `<ir>` parameter set the GPIO associated to an IR receiver. No need to add pullup or capacitor
Syntax is:
```
<gpio>=Vcc|GND|amp[:1|0]|ir|jack[:0|1]|green[:0|1]|red[:0|1]|spkfault[:0|1][,<repeated sequence for next GPIO>]
<gpio>=Vcc|GND|amp[:1|0]|power[:1:0]|ir[:nec|rc5]|jack[:0|1]|green[:0|1|ws2812]|red[:0|1|ws2812]|spkfault[:0|1][,<repeated sequence for next GPIO>]
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for named configurations ((SqueezeAMP, Muse ...) where these are forced at runtime.
**Note that gpio 36 and 39 are input only and cannot use interrupt. When set to jack or speaker fault, a 100ms polling checks their value but that's expensive**
@@ -287,21 +300,21 @@ Each expander can support up to 32 GPIO. To use an expander for buttons, an inte
The parameter "gpio_exp_config" is a semicolon (;) separated list with following syntax for each expander
```
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>|100][,count=<n>|16][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
model=<model>,addr=<addr>,[,port=system|dac][,base=<n>][,count=<n>][,intr=<gpio>][,cs=<gpio>][,speed=<Hz>]
```
- model: pca9535, pca85xx, mcp23017 and mcp23s17 (SPI version)
- addr: chip i2c/spi address (decimal)
- port (I2C): use either "system" port (shared with display for example) or "dac" port (system is default)
- cs (SPI): gpio used for Chip Select
- speed (SPI): speed of the SPI bus for that device (in Hz)
- base: GPIO numbering offset to use everywhere else (default 40)
- base: GPIO numbering offset to use everywhere else (default 40 on esp32 and 48 on esp32-s3)
- count: number of GPIO of expander (default 16 - might be obsolted if model if sufficient to decide)
- intr: real GPIO to use as interrupt.
Note that PWM ("led_brightness" below) is not supported for expanded GPIOs and they cannot be used for high speed or precise timing signals like CS, D/C, Reset and Ready. Buttons, rotary encoder, amplifier control and power are supported. Depending on the actual chipset, pullup or pulldown might be supported so you might have to add external resistors (only MCP23x17 does pullup). The pca8575 is not a great chip, it generate a fair bit of spurious interrupts when used for GPIO out. When using a SPI expander, the bus must be configured using shared [SPI](#SPI) bus
### LED
See [set_GPIO](#set-gpio) for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
See [set_GPIO](#set-gpio) for how to set the green and red LEDs (including addressable RGB ones). In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
@@ -385,9 +398,11 @@ ACTRLS_NONE, ACTRLS_POWER, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_P
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
BCTRLS_PS1, BCTRLS_PS2, BCTRLS_PS3, BCTRLS_PS4, BCTRLS_PS5, BCTRLS_PS6, BCTRLS_PS7, BCTRLS_PS8, BCTRLS_PS9, BCTRLS_PS10,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_SLEEP,
```
Note that ACTRLS_SLEEP is not an actual button that can be sent to LMS, but it's a hook to activate deep sleep mode (see [Sleeping](#sleeping)).
One you've created such a string, use it to fill a new NVS parameter with any name below 16(?) characters. You can have as many of these configs as you can. Then set the config parameter "actrls_config" with the name of your default config
For example a config named "buttons" :
@@ -447,7 +462,7 @@ There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctr
**Note that gpio 36 and 39 are input only and cannot use interrupt. When using them for a button, a 100ms polling is started which is expensive. Long press is also likely to not work very well**
### Ethernet
Wired ethernet is supported by esp32 with various options but squeezelite is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
Wired ethernet is supported by esp32 with various options but squeezeESP32 is only supporting a Microchip LAN8720 with a RMII interface like [this](https://www.aliexpress.com/item/32858432526.html) or SPI-ethernet bridges like Davicom DM9051 [that](https://www.amazon.com/dp/B08JLFWX9Z) or W5500 like [this](https://www.aliexpress.com/item/32312441357.html).
**Note:** Touch buttons that can be find on some board like the LyraT V4.3 are not supported currently.
@@ -492,11 +507,40 @@ model=dm9051|w5500,cs=<gpio>,speed=<clk_in_Hz>,intr=<gpio>[,rst=<gpio>]
### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
```
channel=0..7,scale=<scale>,cells=<2|3>[,atten=<0|1|2|3>]
channel=0..7,scale=<scale>,cells=<1..3>[,atten=<0|1|2|3>]
```
NB: Set parameter to empty to disable battery reading. For named configurations (SqueezeAMP, Muse ...), this is ignored (except for SqueezeAMP where number of cells is required)
# Configuration
### Sleeping
The esp32 can be put in deep sleep mode to save some power. How much really depends on the connected periperals, so best is to do your own measures. Waking-up from deep sleep is the equivalent of a reboot, but as the chip takes a few seconds to connect, it's still an efficient process.
The esp32 can enter deep sleep after an audio inactivity timeout, after a button has been pressed, after a GPIO is set to a given level (there is a subtle difference, see below) or if the battery reaches a threashold. It wakes up only on some GPIO events. Note that *all* GPIO are isolated when sleeping (unless they are set with the `rtc`option) so you can not assume anything about their value, except that they will not drain current. The `rtc` option allows to keep some GPIO (from the RTC domain only) either pulled up or down. This can be useful if you want to keep some periperal active, for example a GPIO expander whose interrupt will be used to wake-up the system.
The NVS parameter `sleep_config` is mostly used for setting sleep conditions
```
[delay=<mins>][,sleep=<gpio>[:0|1]][,wake=<gpio>[:0|1][|<gpio>[:0|1]...][,rtc=<gpio>[:0|1][|<gpio>[:0|1]...][,batt=<voltage>][,spurious=<mins>]
```
- delay: inactivity in **minutes** before going to sleep
- spurious: when using IR, wake-up can be triggered by any activity on the allocated GPIO, hence other remotes may cause unwanted wake-up. This sets (in **minutes** - default is 1) an inactivity delay after which sleep resumes.
- sleep: GPIO that will put the system into sleep and it can be a level 0 or 1.
- wake: **list** of GPIOs that with cause it to wake up (reboot) with their respective values. In such list, GPIO's are separated by an actual '|'.
- batt: threshold in **volts** under which the system will enter into sleep.
The battery voltage is measured every 10 seconds and 30 values are averaged before producing a result. The result must be 3 times below the threshold to enter sleep, so it takes a total of 10\*30\*3 = 15 minutes.
Be mindful that if the same GPIO is used to go to sleep and wakeup with the *same* level (in other word it's a transition/edge that triggers the action) the above will not work and the esp32 will immediately restart. In such case, you case use a button definition. The benefit of buttons is that not only can you re-use one actual button (e.g. 'stop') to make it the sleep trigger (using a long-press or a shift-press) but by selecting the ACTRLS_SLEEP action upon 'release', you can got to sleep upon release (1-0-1 transition) but also wake up upon another press (0 level applied on GPIO) because you only go to sleep *after* the GPIO returned to 1.
Please see [buttons](#buttons) for detailed syntax.
The option to use multiple GPIOs is very limited on esp32 and the esp-idf 4.3.x we are using: it is only possible to wake-up when **any** of the defined GPIO is set to 1. The fact that you can specify different levels in the wake list is irrelevant for now, it's just a provision for future upgrades to more recent versions of esp-idf.
**Only the following GPIOs can be used to wake-up the esp32**
- ESP32: 0, 2, 4, 12-15, 25-27, 32-39;
- ESP32-S3: 0-21.
Some have asked for a soft power on/off option. Although this is not built-in, it's easy to create yours as long as the regulator/power supply of the board can be controlled by Vcc or GND. Depending on how it is active, add a pull-up/down resistor to the regulator's control and connect it also to one GPIO of the esp32. Then using set_GPIO, set that GPIO to Vcc or GND. Use a hardware button that forces the regulator on with a pull- up/down and once the esp32 has booted, it will force the GPIO to the desired value maintaining the board on by software. To power it off by software, just use the deep sleep option which will suspend all GPIO hence switching off the regulator.
# Software configuration
## Setup WiFi
- Boot the esp, look for a new wifi access point showing up and connect to it. Default build ssid and passwords are "squeezelite"/"squeezelite".
@@ -517,6 +561,15 @@ At this point, the device should have disabled its built-in access point and sho
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting (you might have to fiddle with it a few times)
- You can enable accessto NVS parameters under 'credits'
## Spotify
By default, SqueezeESP32 will use ZeroConf to advertise its Spotify capabilties. This means that until at least one local Spotify Connect application controllers discovers and connects to it, SqueezeESP32 will not be registered to Spotify servers. As a consequence, Spotify's WebAPI will not be able to see it (for example, Home Assistant services will miss it). Once you are connected to it using for example Spotify Desktop app, it will be registered and displayed everywhere.
If you want the player to be registered at start-up, you need to disable the ZeroConf option using the WebUI or `cspot_config::ZeroConf`. In that mode, the first time you run SqueezeESP32, it will be in ZeroConf mode and when you connect to it using a controller for the firt time, it receives and store credentials that will be used next time (after reboot).
Set ZeroConf to 1 will always force ZeroConf mode to be used.
The ZeroConf mode consumes less memory as it uses the built-in HTTP and mDNS servers to broadcast its capabilities. A Spotify controller will then discover these and trigger the SqueezeESP32 Spotify stack (cspot) to start. When the controller disconnects, the stack is shut down. In non-ZeroConf mode, the stack starts immediately (providing stored credentials are valid) and always run - a disconnect will not shut it down.
## Monitor
In addition of the esp-idf serial link monitor option, you can also enable a telnet server (see NVS parameters) where you'll have access to a ton of logs of what's happening inside the WROVER.
@@ -542,13 +595,14 @@ For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and
squeezelite -o "BT -n 'BT <sinkname>'" -b 500:2000 -R -u m -Z 192000 -r "44100-44100"
See squeezlite command line, but keys options are
See squeezelite command line, but keys options are
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
- R (see above)
- r "<minrate>-<maxrate>"
- C <sec> : set timeout to switch off amp gpio
- W : activate WAV and AIFF header parsing
- s <name>|-disable: connect to a specific server. Use -disable to not search for any server
**There is a safety feature to protect against WiFi/LMS connection loss that forces a reboot every few minutes when there is no LMS server detected. In case you don't want to use LMS at all, please set the server name to "-disable" on squeezelite command line ("-s -disable")**
@@ -572,11 +626,11 @@ docker run -it -v `pwd`:/workspace/squeezelite-esp32 sle118/squeezelite-esp32-id
The above command will mount this repo into the docker container and start a bash terminal. From there, simply run idf.py build to build, etc. Note that at the time of writing these lines, flashing is not possible for docker running under windows https://github.com/docker/for-win/issues/1018.
### Manual Install of ESP-IDF
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install.
You can install IDF manually on Linux or Windows (using the Subsystem for Linux) following the instructions at: https://www.instructables.com/id/ESP32-Development-on-Windows-Subsystem-for-Linux/ or see here https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/windows-setup.html for a direct install. You also need a few extra Python libraries for cspot by addingsudo `pip3 install protobuf grpcio-tools`
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 **
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
## Building Squeezelite-esp32
## Building SqueezeESP32
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
@@ -609,5 +663,11 @@ If you have already cloned the repository and you are getting compile errors on
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=48000 and unset VAR_ARRAYS in config.h
- libmad has been patched to avoid using a lot of stack and is not provided here. There is an issue with sync detection in 1.15.1b from where the original stack patch was done but since a few fixes have been made wrt sync detection. This 1.15.1b-10 found on debian fixes the issue where mad thinks it has reached sync but has not and so returns a wrong sample rate. It comes at the expense of 8KB (!) of code where a simple check in squeezelite/mad.c that next_frame[0] is 0xff and next_frame[1] & 0xf0 is 0xf0 does the trick ...
# Hardware tips
There is a possibility to have a software on/off where a temporary switch can power-up the esp32 which then will auto-sustain its power. Depending on the selected hardware, it a can also include a power-off by using a long press on the same button.
The auto-power is simply acheived by using `setGPIO` and forcing a GPIO to Vcc or GND and the sustain on/off requires a button creation whose longpress is an ACTRLS_SLEEP action (see also the [Sleeping](#sleeping) section). Credits [Renber78](http://github.com/Renber78) for schedmatics below
![alt text](https://github.com/sle118/squeezelite-esp32/blob/7eb4b218e31aa4692c5280fbec4619f690032c4a/Soft%20Power.png)
# Footnotes
(1) SPDIF is made by tricking the I2S bus but this consumes a fair bit of CPU as it multiplies by four the throughput on the i2s bus. To optimize some computation, the parity of the spdif frames must always be 0, so at least one bit has to be available to force it. As SPDIF samples are 20+4 bits length maximum, the LSB is used for that purpose, so the bit 24 is randomly toggling. It does not matter for 16 bits samples but it has been chosen to truncate the last 4 bits for 24 bits samples. I'm sure that some smart dude can further optimize spdif_convert() and use the user bit instead. You're welcome to do a PR but, as said above, I (philippe44) am not interested by 24 bits mental illness :-) and I've already made an effort to provide 20 bits which already way more what's needed :-)

BIN
Soft Power.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

48
ToggleGitTracking.ps1 Normal file
View File

@@ -0,0 +1,48 @@
param (
[Parameter(Position=0, Mandatory=$false)]
[ValidateSet("t", "u")]
[string]$option
)
# Define the directory to apply changes to
$targetDir = "components\wifi-manager\webapp\dist"
# Get the current directory
$currentDir = Get-Location
# Get list of files from the file system
$fsFiles = Get-ChildItem -Recurse $targetDir -File | ForEach-Object {
$_.FullName.Substring($currentDir.Path.Length + 1).Replace("\", "/")
}
# Get list of files from the Git index
$indexFiles = git ls-files -s $targetDir | ForEach-Object {
($_ -split "\s+")[3]
}
# Combine and remove duplicates
$allFiles = $fsFiles + $indexFiles | Sort-Object -Unique
# Apply the git command based on the option
$allFiles | ForEach-Object {
$relativePath = $_
$isInIndex = $indexFiles -contains $relativePath
if ($null -eq $option) {
$status = if ($isInIndex) { 'tracked' } else { 'not tracked' }
Write-Host "$relativePath is $status"
}
elseif ($isInIndex) {
if ($option -eq "t") {
git update-index --no-skip-worktree $relativePath
Write-Host "Started tracking changes in $relativePath"
}
elseif ($option -eq "u") {
git update-index --skip-worktree $relativePath
Write-Host "Stopped tracking changes in $relativePath"
}
}
else {
Write-Host "File $relativePath is not tracked."
}
}

View File

@@ -297,6 +297,12 @@ CONFIG_AUDIO_CONTROLS=""
CONFIG_AMP_GPIO=-1
# end of AMP configuration
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Audio JACK
#
@@ -942,7 +948,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -264,6 +264,12 @@ CONFIG_CSPOT_SINK=y
#
# end of Display Screen
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Various I/O
#
@@ -901,7 +907,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -288,6 +288,12 @@ CONFIG_AUDIO_CONTROLS=""
CONFIG_AMP_GPIO=-1
# end of AMP configuration
#
# POWER configuration
#
CONFIG_POWER_GPIO=-1
# end of POWER configuration
#
# Compiler options
#
@@ -912,7 +918,7 @@ CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
# CONFIG_FREERTOS_ASSERT_FAIL_ABORT is not set
CONFIG_FREERTOS_ASSERT_DISABLE=y
CONFIG_FREERTOS_ISR_STACKSIZE=2096

View File

@@ -1,6 +1,6 @@
if(IDF_TARGET STREQUAL esp32)
if(IDF_TARGET STREQUAL esp32 AND IDF_VERSION_MAJOR EQUAL 4 AND IDF_VERSION_MINOR LESS 4)
set(lib_dir ${build_dir}/esp-idf)
set(driver esp32/i2s.c esp32/i2s_hal.c)
set(driver esp32/i2s.c)
string(REPLACE ".c" ".c.obj" driver_obj "${driver}")
idf_component_register( SRCS ${driver}
@@ -19,4 +19,6 @@ if(IDF_TARGET STREQUAL esp32)
COMMAND xtensa-esp32-elf-ar -d ${lib_dir}/driver/libdriver.a ${driver_obj}
VERBATIM
)
else()
message(STATUS "==> NO OVERRIDE <==")
endif()

View File

@@ -1,275 +0,0 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The HAL layer for I2S (common part)
#include "soc/soc.h"
#include "soc/soc_caps.h"
#include "hal/i2s_hal.h"
#define I2S_TX_PDM_FP_DEF 960 // Set to the recommended value(960) in TRM
#define I2S_RX_PDM_DSR_DEF 0
void i2s_hal_set_tx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
{
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
} else {
i2s_ll_set_tx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
}
i2s_ll_set_tx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#if SOC_I2S_SUPPORTS_DMA_EQUAL
i2s_ll_set_tx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#endif
}
void i2s_hal_set_rx_mode(i2s_hal_context_t *hal, i2s_channel_t ch, i2s_bits_per_sample_t bits)
{
if (bits <= I2S_BITS_PER_SAMPLE_16BIT) {
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
} else {
i2s_ll_set_rx_fifo_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 2 : 3);
}
i2s_ll_set_rx_chan_mod(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#if SOC_I2S_SUPPORTS_DMA_EQUAL
i2s_ll_set_rx_dma_equal(hal->dev, (ch == I2S_CHANNEL_STEREO) ? 0 : 1);
#endif
}
void i2s_hal_set_in_link(i2s_hal_context_t *hal, uint32_t bytes_num, uint32_t addr)
{
i2s_ll_set_in_link_addr(hal->dev, addr);
i2s_ll_set_rx_eof_num(hal->dev, bytes_num);
}
#if SOC_I2S_SUPPORTS_PDM
void i2s_hal_tx_pdm_cfg(i2s_hal_context_t *hal, uint32_t fp, uint32_t fs)
{
i2s_ll_tx_pdm_cfg(hal->dev, fp, fs);
}
void i2s_hal_get_tx_pdm(i2s_hal_context_t *hal, uint32_t *fp, uint32_t *fs)
{
i2s_ll_get_tx_pdm(hal->dev, fp, fs);
}
void i2s_hal_rx_pdm_cfg(i2s_hal_context_t *hal, uint32_t dsr)
{
i2s_ll_rx_pdm_cfg(hal->dev, dsr);
}
void i2s_hal_get_rx_pdm(i2s_hal_context_t *hal, uint32_t *dsr)
{
i2s_ll_get_rx_pdm(hal->dev, dsr);
}
#endif
void i2s_hal_set_clk_div(i2s_hal_context_t *hal, int div_num, int div_a, int div_b, int tx_bck_div, int rx_bck_div)
{
i2s_ll_set_clkm_div_num(hal->dev, div_num);
i2s_ll_set_clkm_div_a(hal->dev, div_a);
i2s_ll_set_clkm_div_b(hal->dev, div_b);
i2s_ll_set_tx_bck_div_num(hal->dev, tx_bck_div);
i2s_ll_set_rx_bck_div_num(hal->dev, rx_bck_div);
}
void i2s_hal_set_tx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
{
i2s_ll_set_tx_bits_mod(hal->dev, bits);
}
void i2s_hal_set_rx_bits_mod(i2s_hal_context_t *hal, i2s_bits_per_sample_t bits)
{
i2s_ll_set_rx_bits_mod(hal->dev, bits);
}
void i2s_hal_reset(i2s_hal_context_t *hal)
{
// Reset I2S TX/RX module first, and then, reset DMA and FIFO.
i2s_ll_reset_tx(hal->dev);
i2s_ll_reset_rx(hal->dev);
i2s_ll_reset_dma_in(hal->dev);
i2s_ll_reset_dma_out(hal->dev);
i2s_ll_reset_rx_fifo(hal->dev);
i2s_ll_reset_tx_fifo(hal->dev);
}
void i2s_hal_start_tx(i2s_hal_context_t *hal)
{
i2s_ll_start_out_link(hal->dev);
i2s_ll_start_tx(hal->dev);
}
void i2s_hal_start_rx(i2s_hal_context_t *hal)
{
i2s_ll_start_in_link(hal->dev);
i2s_ll_start_rx(hal->dev);
}
void i2s_hal_stop_tx(i2s_hal_context_t *hal)
{
i2s_ll_stop_out_link(hal->dev);
i2s_ll_stop_tx(hal->dev);
}
void i2s_hal_stop_rx(i2s_hal_context_t *hal)
{
i2s_ll_stop_in_link(hal->dev);
i2s_ll_stop_rx(hal->dev);
}
void i2s_hal_format_config(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
{
switch (i2s_config->communication_format) {
case I2S_COMM_FORMAT_STAND_MSB:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_format_msb_align(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_format_msb_align(hal->dev);
}
break;
case I2S_COMM_FORMAT_STAND_PCM_SHORT:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_pcm_long(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_pcm_long(hal->dev);
}
break;
case I2S_COMM_FORMAT_STAND_PCM_LONG:
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_pcm_short(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_pcm_short(hal->dev);
}
break;
default: //I2S_COMM_FORMAT_STAND_I2S
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_set_tx_format_philip(hal->dev);
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_format_philip(hal->dev);
}
break;
}
}
void i2s_hal_config_param(i2s_hal_context_t *hal, const i2s_config_t *i2s_config)
{
//reset i2s
i2s_ll_reset_tx(hal->dev);
i2s_ll_reset_rx(hal->dev);
//reset dma
i2s_ll_reset_dma_in(hal->dev);
i2s_ll_reset_dma_out(hal->dev);
i2s_ll_enable_dma(hal->dev);
i2s_ll_set_lcd_en(hal->dev, 0);
i2s_ll_set_camera_en(hal->dev, 0);
i2s_ll_set_dscr_en(hal->dev, 0);
i2s_ll_set_tx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
i2s_ll_set_tx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
i2s_ll_set_tx_mono(hal->dev, 0);
i2s_ll_set_rx_chan_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? i2s_config->channel_format : (i2s_config->channel_format >> 1)); // 0-two channel;1-right;2-left;3-righ;4-left
i2s_ll_set_rx_fifo_mod(hal->dev, i2s_config->channel_format < I2S_CHANNEL_FMT_ONLY_RIGHT ? 0 : 1); // 0-right&left channel;1-one channel
i2s_ll_set_rx_mono(hal->dev, 0);
i2s_ll_set_dscr_en(hal->dev, 1); //connect dma to fifo
i2s_ll_stop_tx(hal->dev);
i2s_ll_stop_rx(hal->dev);
if (i2s_config->mode & I2S_MODE_TX) {
int order = i2s_config->bits_per_sample == 32 ? 0 : 1;
i2s_ll_set_tx_msb_right(hal->dev, order);
i2s_ll_set_tx_right_first(hal->dev, ~order);
i2s_ll_set_tx_slave_mod(hal->dev, 0); // Master
i2s_ll_set_tx_fifo_mod_force_en(hal->dev, 1);
if (i2s_config->mode & I2S_MODE_SLAVE) {
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
}
}
if (i2s_config->mode & I2S_MODE_RX) {
i2s_ll_set_rx_msb_right(hal->dev, 0);
i2s_ll_set_rx_right_first(hal->dev, 0);
i2s_ll_set_rx_slave_mod(hal->dev, 0); // Master
i2s_ll_set_rx_fifo_mod_force_en(hal->dev, 1);
if (i2s_config->mode & I2S_MODE_SLAVE) {
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
}
#if SOC_I2S_SUPPORTS_PDM
if (!(i2s_config->mode & I2S_MODE_PDM)) {
i2s_ll_set_rx_pdm_en(hal->dev, 0);
i2s_ll_set_tx_pdm_en(hal->dev, 0);
} else {
if (i2s_config->mode & I2S_MODE_TX) {
i2s_ll_tx_pdm_cfg(hal->dev, I2S_TX_PDM_FP_DEF, i2s_config->sample_rate/100);
}
if(i2s_config->mode & I2S_MODE_RX) {
i2s_ll_rx_pdm_cfg(hal->dev, I2S_RX_PDM_DSR_DEF);
}
// PDM mode have nothing to do with communication format configuration.
return;
}
#endif
#if SOC_I2S_SUPPORTS_ADC_DAC
if (i2s_config->mode & (I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN)) {
if (i2s_config->mode & I2S_MODE_DAC_BUILT_IN) {
i2s_ll_build_in_dac_ena(hal->dev);
}
if (i2s_config->mode & I2S_MODE_ADC_BUILT_IN) {
i2s_ll_build_in_adc_ena(hal->dev);
i2s_ll_set_rx_chan_mod(hal->dev, 1);
i2s_ll_set_rx_fifo_mod(hal->dev, 1);
i2s_ll_set_rx_mono(hal->dev, 0);
}
// Buildin ADC and DAC have nothing to do with communication format configuration.
return;
}
#endif
i2s_hal_format_config(hal, i2s_config);
}
void i2s_hal_enable_master_mode(i2s_hal_context_t *hal)
{
i2s_ll_set_tx_slave_mod(hal->dev, 0); //MASTER Slave
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
void i2s_hal_enable_slave_mode(i2s_hal_context_t *hal)
{
i2s_ll_set_tx_slave_mod(hal->dev, 1); //TX Slave
i2s_ll_set_rx_slave_mod(hal->dev, 1); //RX Slave
}
void i2s_hal_init(i2s_hal_context_t *hal, int i2s_num)
{
//Get hardware instance.
hal->dev = I2S_LL_GET_HW(i2s_num);
}

View File

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

View File

@@ -19,6 +19,12 @@
#include "gds.h"
#include "gds_private.h"
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
#else
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
#endif
static struct GDS_Device Display;
static struct GDS_BacklightPWM PWMConfig;
@@ -34,7 +40,7 @@ struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], s
ledc_timer_config_t PWMTimer = {
.duty_resolution = LEDC_TIMER_13_BIT,
.freq_hz = 5000,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_SPEED_MODE,
.timer_num = PWMConfig.Timer,
};
ledc_timer_config(&PWMTimer);
@@ -188,7 +194,7 @@ bool GDS_Init( struct GDS_Device* Device ) {
.channel = Device->Backlight.Channel,
.duty = Device->Backlight.PWM,
.gpio_num = Device->Backlight.Pin,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_SPEED_MODE,
.hpoint = 0,
.timer_sel = PWMConfig.Timer,
};
@@ -231,8 +237,8 @@ void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
if (Device->SetContrast) Device->SetContrast( Device, Contrast );
else if (Device->Backlight.Pin >= 0) {
Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );
ledc_set_duty( LEDC_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
ledc_update_duty( LEDC_SPEED_MODE, Device->Backlight.Channel );
}
}

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,6 @@
* Driver does support other led device. Maybe look at supporting in future.
* The VU refresh rate has been decreaced (100->75) to optimize animation of spin dial. Could make
* configurable like text scrolling (or use the same value)
* Look at reserving a status led within the effects. (may require nvs setting for center or end position)
* Artwork function, but not released as very buggy and not really practical
*/
@@ -21,14 +20,19 @@
#include <math.h>
#include "esp_log.h"
#include "globdefs.h"
#include "monitor.h"
#include "led_strip.h"
#include "platform_config.h"
#include "led_vu.h"
static const char *TAG = "led_vu";
#define LED_VU_STACK_SIZE (3*1024)
#define LED_VU_RMT_INTR_NUM 20U
static void (*battery_handler_chain)(float value, int cells);
static void battery_svc(float value, int cells);
static int battery_status = 0;
#define LED_VU_STACK_SIZE (3*1024)
#define LED_VU_PEAK_HOLD 6U
@@ -36,15 +40,13 @@ static const char *TAG = "led_vu";
#define LED_VU_DEFAULT_LENGTH 19
#define LED_VU_MAX_LENGTH 255
#define LED_VU_STATUS_GREEN 75
#define LED_VU_STATUS_RED 25
#define max(a,b) (((a) > (b)) ? (a) : (b))
struct led_strip_t* led_display = NULL;
static EXT_RAM_ATTR struct led_strip_t led_strip_config = {
.rgb_led_type = RGB_LED_TYPE_WS2812,
.rmt_channel = RMT_CHANNEL_1,
.rmt_interrupt_num = LED_VU_RMT_INTR_NUM,
.gpio = -1,
};
static EXT_RAM_ATTR struct led_strip_t led_strip_config;
static EXT_RAM_ATTR struct {
int gpio;
@@ -52,7 +54,7 @@ static EXT_RAM_ATTR struct {
int vu_length;
int vu_start_l;
int vu_start_r;
int vu_odd;
int vu_status;
} strip;
static int led_addr(int pos ) {
@@ -61,6 +63,13 @@ static int led_addr(int pos ) {
return pos;
}
static void battery_svc(float value, int cells) {
battery_status = battery_level_svc();
ESP_LOGI(TAG, "Called for battery service with volt:%f cells:%d status:%d", value, cells, battery_status);
if (battery_handler_chain) battery_handler_chain(value, cells);
}
/****************************************************************************************
* Initialize the led vu strip if configured.
*
@@ -86,34 +95,48 @@ void led_vu_init()
ESP_LOGI(TAG, "led_vu configuration invalid");
goto done;
}
battery_handler_chain = battery_handler_svc;
battery_handler_svc = battery_svc;
battery_status = battery_level_svc();
if (strip.length > LED_VU_MAX_LENGTH) strip.length = LED_VU_MAX_LENGTH;
// initialize vu settings
//strip.vu_length = (strip.length % 2) ? strip.length / 2 : (strip.length - 1) / 2;
strip.vu_length = (strip.length - 1) / 2;
strip.vu_start_l = strip.vu_length;
strip.vu_start_r = strip.vu_start_l + 1;
strip.vu_odd = strip.length - 1;
// initialize vu meter settings
if (strip.length < 10) {
// single bar for small strips
strip.vu_length = strip.length;
strip.vu_start_l = 0;
strip.vu_start_r = strip.vu_start_l;
strip.vu_status = 0;
} else {
strip.vu_length = (strip.length - 1) / 2;
strip.vu_start_l = (strip.length % 2) ? strip.vu_length -1 : strip.vu_length;
strip.vu_start_r = strip.vu_length + 1;
strip.vu_status = strip.vu_length;
}
ESP_LOGI(TAG, "vu meter using length:%d left:%d right:%d status:%d", strip.vu_length, strip.vu_start_l, strip.vu_start_r, strip.vu_status);
// create driver configuration
led_strip_config.rgb_led_type = RGB_LED_TYPE_WS2812;
led_strip_config.access_semaphore = xSemaphoreCreateBinary();
led_strip_config.led_strip_length = strip.length;
led_strip_config.led_strip_working = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
led_strip_config.led_strip_showing = heap_caps_malloc(strip.length * sizeof(struct led_color_t), MALLOC_CAP_8BIT);
led_strip_config.gpio = strip.gpio;
led_strip_config.rmt_channel = RMT_NEXT_TX_CHANNEL();
// initialize driver
bool led_init_ok = led_strip_init(&led_strip_config);
if (led_init_ok) {
led_display = &led_strip_config;
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d", strip.gpio, strip.length);
ESP_LOGI(TAG, "led_vu using gpio:%d length:%d on channel:%d", strip.gpio, strip.length, led_strip_config.rmt_channel);
} else {
ESP_LOGE(TAG, "led_vu init failed");
goto done;
}
// reserver max memory for remote management systems
rmt_set_mem_block_num(RMT_CHANNEL_1, 7);
rmt_set_mem_block_num(led_strip_config.rmt_channel, 7);
led_vu_clear(led_display);
@@ -299,7 +322,11 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) {
static int decay_r = 0;
if (!led_display) return;
// single bar
if (strip.vu_start_l == strip.vu_start_r) {
vu_r = (vu_l + vu_r) / 2;
vu_l = 0;
}
// scale vu samples to length
vu_l = vu_l * strip.vu_length / bright;
@@ -360,6 +387,14 @@ void led_vu_display(int vu_l, int vu_r, int bright, bool comet) {
g = (g < step) ? 0 : g - step;
}
// show battery status
if (battery_status > LED_VU_STATUS_GREEN)
led_strip_set_pixel_rgb(led_display, strip.vu_status, 0, bright, 0);
else if (battery_status > LED_VU_STATUS_RED)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright/2, bright/2, 0);
else if (battery_status > 0)
led_strip_set_pixel_rgb(led_display, strip.vu_status, bright, 0, 0);
led_strip_show(led_display);
}

View File

@@ -0,0 +1,124 @@
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "Batch.h"
#include "esp_event.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_ota_ops.h"
#include "esp_tls.h"
#include "nvs_flash.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
#include "esp_crt_bundle.h"
#endif
#include "esp_system.h"
#include "http_handlers.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "nvs_utilities.h"
#include "tools.h"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
#include <sys/param.h>
#if CONFIG_WITH_METRICS
static const char* const TAG = "MetricsBatch";
static const char* const feature_evt_name = "$feature_flag_called";
static const char* const feature_flag_name = "$feature_flag";
static const char* const feature_flag_response_name = "$feature_flag_response";
namespace Metrics {
Event& Batch::add_feature_event() { return add_event(feature_evt_name); }
void Batch::add_remove_feature_event(const char* name, bool active) {
if (!active) {
remove_feature_event(name);
} else {
add_event(feature_evt_name).add_property(feature_flag_name, name);
}
}
Event& Batch::add_feature_variant_event(const char* const name, const char* const value) {
return add_event(feature_evt_name)
.add_property(feature_flag_name, name)
.add_property(feature_flag_response_name, value);
}
void Batch::remove_feature_event(const char* name) {
for (Metrics::Event& e : _events) {
if (strcmp(e.get_name(), feature_evt_name) == 0) {
e.remove_property(feature_flag_name, name);
return;
}
}
}
cJSON* Batch::to_json() {
cJSON* batch_json = cJSON_CreateArray();
for (Metrics::Event& e : _events) {
cJSON_AddItemToArray(batch_json, e.to_json(_metrics_uid.c_str()));
}
cJSON* message = cJSON_CreateObject();
cJSON_AddItemToObject(message, "batch", batch_json);
cJSON_AddStringToObject(message, "api_key", _api_key);
return batch_json;
}
char* Batch::to_json_str() {
cJSON* json = to_json();
char* json_str = cJSON_PrintUnformatted(json);
cJSON_Delete(json);
return json_str;
}
void Batch::push() {
int status_code = 0;
if (_metrics_uid.empty() && !_warned) {
ESP_LOGW(TAG, "Metrics disabled; no CID found");
_warned = true;
return;
}
char* json_str = to_json_str();
ESP_LOGV(TAG, "Metrics payload: %s", json_str);
uint32_t start_time = gettime_ms();
status_code = metrics_http_post_request(json_str, _url);
if (status_code == 200 || status_code == 204) {
_events.clear();
}
FREE_AND_NULL(json_str)
ESP_LOGD(TAG, "Total duration for metrics call: %lu. ", gettime_ms() - start_time);
}
void Batch::build_guid() {
uint8_t raw[16];
std::ostringstream oss;
esp_fill_random(raw, 16);
std::for_each(std::begin(raw), std::end(raw), [&oss](const uint8_t& byte) {
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte);
});
_metrics_uid = oss.str();
}
void Batch::assign_id() {
size_t size = 0;
esp_err_t esp_err = ESP_OK;
_metrics_uid = std::string((char*)get_nvs_value_alloc_for_partition(
NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB, "cid", &size));
if (_metrics_uid[0] == 'G') {
ESP_LOGW(TAG, "Invalid ID. %s", _metrics_uid.c_str());
_metrics_uid.clear();
}
if (_metrics_uid.empty()) {
build_guid();
if (_metrics_uid.empty()) {
ESP_LOGE(TAG, "ID Failed");
return;
}
ESP_LOGW(TAG, "Metrics ID: %s", _metrics_uid.c_str());
esp_err = store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, TAG, NVS_TYPE_BLOB,
"cid", _metrics_uid.c_str(), _metrics_uid.length() + 1);
if (esp_err != ESP_OK) {
ESP_LOGE(TAG, "Store ID failed: %s", esp_err_to_name(esp_err));
}
}
}
} // namespace Metrics
#endif

View File

@@ -0,0 +1,46 @@
#pragma once
#include "Events.h"
#include <string>
#ifdef __cplusplus
namespace Metrics {
extern "C" {
#endif
#ifdef __cplusplus
class Batch {
private:
std::list<Event> _events;
bool _warned = false;
std::string _metrics_uid = nullptr;
const char* _api_key = nullptr;
const char* _url = nullptr;
void build_guid();
void assign_id();
public:
Batch() = default;
void configure(const char* api_key, const char* url) {
_api_key = api_key;
_url = url;
assign_id();
}
Event& add_feature_event();
void add_remove_feature_event(const char* name, bool active);
Event& add_feature_variant_event(const char* const name, const char* const value);
Event& add_event(const char* name) {
_events.emplace_back(name);
return _events.back();
}
bool has_events() const { return !_events.empty(); }
void remove_feature_event(const char* name);
cJSON* to_json();
char* to_json_str();
void push();
};
}
#endif
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,5 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES json tools platform_config wifi-manager esp-tls platform_config
PRIV_REQUIRES esp32 freertos
)

View File

@@ -0,0 +1,98 @@
#include "Events.h"
#include <algorithm>
#include "esp_app_format.h"
#include "esp_ota_ops.h"
#if CONFIG_WITH_METRICS
static const char* const TAG = "MetricsEvent";
namespace Metrics {
Event& Event::add_property(const char* name, const char* value) {
ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
char* mutable_name = strdup_psram(name); // Cast away const-ness, be careful with this
auto elem = properties.find(mutable_name);
FREE_AND_NULL(mutable_name)
if (elem == properties.end()) {
ESP_LOGV(TAG, "Adding property %s:%s to event %s",name,value,_name);
properties.insert({strdup_psram(name), strdup_psram(value)});
} else {
ESP_LOGV(TAG, "Replacing value for property %s. Old: %s New: %s, Event: %s",name,elem->second,value,name);
FREE_AND_NULL(elem->second)
elem->second = strdup_psram(value);
}
return *this;
}
bool Event::has_property_value(const char* name, const char* value) const {
ESP_LOGV(TAG, "Checking if event %s property %s has value %s",_name, name,value);
return std::any_of(properties.begin(), properties.end(),
[name, value](const std::pair<const char* const, char*>& kv) {
ESP_LOGV(TAG, "Found property %s=%s", name,value);
return strcmp(kv.first, name) == 0 && strcmp(kv.second, value) == 0;
});
}
void Event::remove_property(const char* name, const char* value) {
auto it = properties.begin();
ESP_LOGV(TAG, "Removing event %s property %s=%s",_name, name,value);
while (it != properties.end()) {
if (strcmp(it->first, name) == 0 && strcmp(it->second, value)) {
properties.erase(it);
return;
}
}
ESP_LOGV(TAG, "Property %s=%s not found.", name,value);
}
cJSON* Event::properties_to_json() {
ESP_LOGV(TAG, "Event %s properties to json.",_name);
const esp_app_desc_t* desc = esp_ota_get_app_description();
#ifdef CONFIG_FW_PLATFORM_NAME
const char* platform = CONFIG_FW_PLATFORM_NAME;
#else
const char* platform = desc->project_name;
#endif
cJSON* prop_json = cJSON_CreateObject();
auto it = properties.begin();
while (it != properties.end()) {
cJSON_AddStringToObject(prop_json, it->first, it->second);
++it;
}
cJSON_AddStringToObject(prop_json, "platform", platform);
cJSON_AddStringToObject(prop_json, "build", desc->version);
dump_json_content("User properties for event:", prop_json, ESP_LOG_VERBOSE);
return prop_json;
}
cJSON* Event::to_json(const char* distinct_id) {
// The target structure looks like this
// {
// "event": "batched_event_name_1",
// "properties": {
// "distinct_id": "user distinct id",
// "account_type": "pro"
// },
// "timestamp": "[optional timestamp in ISO 8601 format]"
// }
ESP_LOGV(TAG,"Event %s to json",_name);
free_json();
_json = cJSON_CreateObject();
cJSON_AddStringToObject(_json, "name", _name);
cJSON_AddItemToObject(_json, "properties", properties_to_json());
char buf[26] = {};
strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&_time));
// this will work too, if your compiler doesn't support %F or %T:
// strftime(buf, sizeof buf, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
cJSON_AddStringToObject(_json, "timestamp", buf);
cJSON* prop_json = properties_to_json();
cJSON_AddStringToObject(prop_json, "distinct_id", distinct_id);
dump_json_content("Full Event:", _json, ESP_LOG_VERBOSE);
return _json;
}
void Event::free_json() { cJSON_Delete(_json); }
void Event::update_time() {
if (_time == 0) {
_time = time(nullptr);
}
}
} // namespace Metrics
#endif

View File

@@ -0,0 +1,53 @@
#pragma once
#ifdef __cplusplus
#include "esp_log.h"
#include "tools.h"
#include <cJSON.h>
#include <ctime>
#include <list>
#include <map>
#include <stdio.h>
#include <string.h>
#include <string>
namespace Metrics {
struct StrCompare {
bool operator()(const char* a, const char* b) const { return strcmp(a, b) < 0; }
};
class Event {
public:
std::map<char*, char*, StrCompare> properties;
Event& add_property(const char* name, const char* value);
bool has_property_value(const char* name, const char* value) const;
void remove_property(const char* name, const char* value);
cJSON* properties_to_json();
cJSON* to_json(const char* distinct_id);
void free_json();
void update_time();
explicit Event(const char* name) {
_name = strdup_psram(name);
memset(&_time, 0x00, sizeof(_time));
}
const char* get_name() const { return _name; }
~Event() {
FREE_AND_NULL(_name);
// Iterate through the map and free the elements
for (auto& kv : properties) {
free((void*)kv.first);
free(kv.second);
}
properties.clear(); // Clear the map after freeing memory
FREE_AND_NULL(_json);
}
private:
char* _name = nullptr;
uint32_t _time;
cJSON* _json = nullptr;
};
} // namespace Metrics
#endif

View File

@@ -0,0 +1,148 @@
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "Metrics.h"
#include "Batch.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_ota_ops.h"
#include "esp_system.h"
#include "esp_tls.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "tools.h"
#include <cstdarg>
#include <cstdio>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
#include "cJSON.h"
#include "freertos/timers.h"
#include "network_manager.h"
#include "platform_config.h"
static const char* TAG = "metrics";
#if CONFIG_WITH_METRICS
extern bool is_network_connected();
#define METRICS_CLIENT_ID_LEN 50
#define MAX_HTTP_RECV_BUFFER 512
static bool metrics_usage_gen = false;
static uint32_t metrics_usage_gen_time = 0;
#ifndef METRICS_API_KEY
#pragma message "Metrics API key needs to be passed from the environment"
#define METRICS_API_KEY "ZZZ"
#endif
static const char* metrics_api_key =
static const char* parms_str = "params";
static const char* properties_str = "properties";
static const char* user_properties_str = "user_properties";
static const char* items_str = "items";
static const char* quantity_str = "quantity";
static const char* metrics_url = "https://app.posthog.com";
static TimerHandle_t timer;
extern cJSON* get_cmd_list();
Metrics::Batch batch;
static void metrics_timer_cb(void* timer_id) {
if (batch.has_events()) {
if (!is_network_connected()) {
ESP_LOGV(TAG, "Network not connected. can't flush");
} else {
ESP_LOGV(TAG, "Pushing events");
batch.push();
}
}
if (gettime_ms() > metrics_usage_gen_time && !metrics_usage_gen) {
metrics_usage_gen = true;
ESP_LOGV(TAG, "Generate command list to pull features");
cJSON* cmdlist = get_cmd_list();
dump_json_content("generated cmd list", cmdlist, ESP_LOG_VERBOSE);
cJSON_Delete(cmdlist);
}
}
void metrics_init() {
ESP_LOGV(TAG, "Initializing metrics");
batch.configure(metrics_api_key, metrics_url);
if (!timer) {
ESP_LOGE(TAG, "Metrics Timer failure");
} else {
ESP_LOGV(TAG, "Starting timer");
xTimerStart(timer, portMAX_DELAY);
}
// set a 20 seconds delay before generating the
// features so the system has time to boot
metrics_usage_gen_time = gettime_ms() + 20000;
}
void metrics_event_playback(const char* source) {
ESP_LOGV(TAG, "Playback event: %s", source);
auto event = batch.add_event("play").add_property("source", source);
}
void metrics_event_boot(const char* partition) {
ESP_LOGV(TAG, "Boot event %s", partition);
auto event = batch.add_event("start");
event.add_property("partition", partition);
}
void metrics_add_feature_variant(const char* name, const char* format, ...) {
va_list args;
ESP_LOGV(TAG, "Feature %s", name);
va_start(args, format);
// Determine the required buffer size
int size = vsnprintf(nullptr, 0, format, args);
va_end(args); // Reset the va_list
// Allocate buffer and format the string
std::vector<char> buffer(size + 1); // +1 for the null-terminator
va_start(args, format);
vsnprintf(buffer.data(), buffer.size(), format, args);
va_end(args);
// Now buffer.data() contains the formatted string
batch.add_feature_variant_event(name, buffer.data());
}
void metrics_add_feature(const char* name, bool active) {
ESP_LOGV(TAG, "Adding feature %s: %s", name, active ? "ACTIVE" : "INACTIVE");
batch.add_remove_feature_event(name, active);
}
void metrics_event(const char* name) {
ESP_LOGV(TAG, "Adding Event %s", name);
batch.add_event(name);
}
#else
static const char * not_enabled = " - (metrics not enabled, this is just marking where the call happens)";
void metrics_init(){
#pragma message("Metrics disabled")
ESP_LOGD(TAG,"Metrics init%s",not_enabled);
}
void metrics_event_boot(const char* partition){
ESP_LOGD(TAG,"Metrics Event Boot from partition %s%s",partition,not_enabled);
}
void metrics_event(const char* name){
ESP_LOGD(TAG,"Metrics Event %s%s",name,not_enabled);
}
void metrics_add_feature(const char* name, bool active) {
ESP_LOGD(TAG,"Metrics add feature %s%s%s",name,active?"ACTIVE":"INACTIVE",not_enabled);
}
void metrics_add_feature_variant(const char* name, const char* format, ...){
va_list args;
ESP_LOGV(TAG, "Feature %s", name);
va_start(args, format);
// Determine the required buffer size
int size = vsnprintf(nullptr, 0, format, args);
va_end(args); // Reset the va_list
// Allocate buffer and format the string
std::vector<char> buffer(size + 1); // +1 for the null-terminator
va_start(args, format);
vsnprintf(buffer.data(), buffer.size(), format, args);
va_end(args);
ESP_LOGD(TAG,"Metrics add feature %s variant %s%s",name,buffer.data(),not_enabled);
}
#endif

View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
void metrics_event_playback(const char* source);
void metrics_event_boot(const char* partition);
void metrics_event(const char* name);
void metrics_add_feature(const char* name, bool active);
void metrics_add_feature_variant(const char* name, const char* format, ...);
void metrics_init();
void metrics_flush();
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,163 @@
#include "http_handlers.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_tls.h"
#include "tools.h"
#include <sys/param.h>
#if CONFIG_WITH_METRICS
static const char* TAG = "metrics_http";
static char* output_buffer; // Buffer to store response of http request from
// event handler
static int output_len = 0; // Stores number of bytes read
#define MAX_HTTP_OUTPUT_BUFFER 2048
// Common function signature for event handlers
typedef void (*HttpEventHandler)(esp_http_client_event_t* evt);
static void handle_http_error(esp_http_client_event_t* evt) { ESP_LOGV(TAG, "ERROR"); }
static void handle_http_connected(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_CONNECTED");
}
static void handle_http_header_sent(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "HEADER_SENT");
}
static void handle_http_on_header(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
}
static void handle_http_on_data(esp_http_client_event_t* evt) {
ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
ESP_LOGV(TAG, "ON_DATA, len=%d", evt->data_len);
// Clean the buffer in case of a new request
if (output_len == 0 && evt->user_data) {
// we are just starting to copy the output data into the use
ESP_LOGV(TAG, "Resetting buffer");
memset(evt->user_data, 0, MAX_HTTP_OUTPUT_BUFFER);
}
/*
* Check for chunked encoding is added as the URL for chunked encoding used in this example
* returns binary data. However, event handler can also be used in case chunked encoding is
* used.
*/
// If user_data buffer is configured, copy the response into the buffer
int copy_len = 0;
if (evt->user_data) {
ESP_LOGV(TAG, "Not Chunked response, with user data");
// The last byte in evt->user_data is kept for the NULL character in
// case of out-of-bound access.
copy_len = MIN(evt->data_len, (MAX_HTTP_OUTPUT_BUFFER - output_len));
if (copy_len) {
memcpy(evt->user_data + output_len, evt->data, copy_len);
}
} else {
int content_len = esp_http_client_get_content_length(evt->client);
if (esp_http_client_is_chunked_response(evt->client)) {
esp_http_client_get_chunk_length(evt->client, &content_len);
}
if (output_buffer == NULL) {
// We initialize output_buffer with 0 because it is used by
// strlen() and similar functions therefore should be null
// terminated.
size_t len=(content_len + 1) * sizeof(char);
ESP_LOGV(TAG, "Init buffer %d",len);
output_buffer = (char*)malloc_init_external(len);
output_len = 0;
if (output_buffer == NULL) {
ESP_LOGE(TAG, "Buffer alloc failed.");
return;
}
}
copy_len = MIN(evt->data_len, (content_len - output_len));
if (copy_len) {
memcpy(output_buffer + output_len, evt->data, copy_len);
}
}
output_len += copy_len;
}
static void handle_http_on_finish(esp_http_client_event_t* evt) {
ESP_LOGD(TAG, "ON_FINISH");
if (output_buffer != NULL) {
ESP_LOGV(TAG, "Response: %s", output_buffer);
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
}
static void handle_http_disconnected(esp_http_client_event_t* evt) {
ESP_LOGI(TAG, "DISCONNECTED");
int mbedtls_err = 0;
esp_err_t err =
esp_tls_get_and_clear_last_error((esp_tls_error_handle_t)evt->data, &mbedtls_err, NULL);
if (err != 0) {
ESP_LOGI(TAG, "Last error : %s", esp_err_to_name(err));
ESP_LOGI(TAG, "Last mbedtls err 0x%x", mbedtls_err);
}
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
}
static const HttpEventHandler eventHandlers[] = {
handle_http_error, // HTTP_EVENT_ERROR
handle_http_connected, // HTTP_EVENT_ON_CONNECTED
handle_http_header_sent, // HTTP_EVENT_HEADERS_SENT
handle_http_header_sent, // HTTP_EVENT_HEADER_SENT (alias for HTTP_EVENT_HEADERS_SENT)
handle_http_on_header, // HTTP_EVENT_ON_HEADER
handle_http_on_data, // HTTP_EVENT_ON_DATA
handle_http_on_finish, // HTTP_EVENT_ON_FINISH
handle_http_disconnected // HTTP_EVENT_DISCONNECTED
};
esp_err_t metrics_http_event_handler(esp_http_client_event_t* evt) {
if (evt->event_id < 0 || evt->event_id >= sizeof(eventHandlers) / sizeof(eventHandlers[0])) {
ESP_LOGE(TAG, "Invalid event ID: %d", evt->event_id);
return ESP_FAIL;
}
eventHandlers[evt->event_id](evt);
return ESP_OK;
}
int metrics_http_post_request(const char* payload, const char* url) {
int status_code = 0;
esp_http_client_config_t config = {.url = url,
.disable_auto_redirect = false,
.event_handler = metrics_http_event_handler,
.transport_type = HTTP_TRANSPORT_OVER_SSL,
.user_data = NULL, // local_response_buffer, // Pass address of
// local buffer to get response
.skip_cert_common_name_check = true
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_set_method(client, HTTP_METHOD_POST);
if (err == ESP_OK) {
err = esp_http_client_set_header(client, "Content-Type", "application/json");
}
if (err == ESP_OK) {
ESP_LOGV(TAG, "Setting payload: %s", payload);
err = esp_http_client_set_post_field(client, payload, strlen(payload));
}
if (err == ESP_OK) {
err = esp_http_client_perform(client);
}
if (err == ESP_OK) {
status_code = esp_http_client_get_status_code(client);
ESP_LOGD(TAG, "metrics call Status = %d, content_length = %d",
esp_http_client_get_status_code(client), esp_http_client_get_content_length(client));
} else {
status_code = 500;
ESP_LOGW(TAG, "metrics call Status failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
return status_code;
}
#endif

View File

@@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
int metrics_http_post_request(const char* payload, const char* url);
#ifdef __cplusplus
}
#endif

View File

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

View File

@@ -538,7 +538,7 @@ bool config_set_group_bit(int bit_num,bool flag){
return result;
}
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size) {
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size) {
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
ESP_LOGE(TAG, "Unable to lock config");
return;
@@ -634,7 +634,7 @@ cJSON * config_alloc_get_cjson(const char *key){
}
return conf_json;
}
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson){
char * value_str = cJSON_PrintUnformatted(value);
if(value_str==NULL){
ESP_LOGE(TAG, "Unable to print cJSON for key [%s]", key);
@@ -642,9 +642,14 @@ esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
}
esp_err_t err = config_set_value(NVS_TYPE_STR,key, value_str);
free(value_str);
cJSON_Delete(value);
if(free_cjson){
cJSON_Delete(value);
}
return err;
}
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value){
return config_set_cjson(key, value, true);
}
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value){
char * str_value = config_alloc_get(NVS_TYPE_STR, key);
if(str_value == NULL){
@@ -786,6 +791,7 @@ cJSON* cjson_update_number(cJSON** root, const char* key, int value) {
}
return *root;
}
IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8);
IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8);
IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16);

View File

@@ -9,23 +9,23 @@
extern "C" {
#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(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_FLOAT(S,P,C,V) do { \
char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__p+1); \
} while (0)
#define PARSE_PARAM_FLOAT(S,P,C,V) do { \
char *__p; \
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atof(__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 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);
@@ -54,9 +54,10 @@ void * config_alloc_get_default(nvs_type_t type, const char *key, void * default
void * config_alloc_get_str(const char *key, char *lead, char *fallback);
cJSON * config_alloc_get_cjson(const char *key);
esp_err_t config_set_cjson_str_and_free(const char *key, cJSON *value);
esp_err_t config_set_cjson(const char *key, cJSON *value, bool free_cjson);
void config_get_uint16t_from_str(const char *key, uint16_t *value, uint16_t default_value);
void config_delete_key(const char *key);
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size);
void config_set_default(nvs_type_t type, const char *key, const void * default_value, size_t blob_size);
void * config_alloc_get(nvs_type_t nvs_type, const char *key) ;
bool wait_for_commit();
char * config_alloc_get_json(bool bFormatted);

View File

@@ -8,7 +8,7 @@ idf_component_register( SRCS
cmd_config.c
INCLUDE_DIRS .
REQUIRES nvs_flash
PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools)
PRIV_REQUIRES console app_update tools services spi_flash platform_config vfs pthread wifi-manager platform_config newlib telnet display squeezelite tools metrics)
set_source_files_properties(cmd_config.c
PROPERTIES COMPILE_FLAGS

View File

@@ -1,6 +1,6 @@
idf_component_register( SRC_DIRS .
INCLUDE_DIRS .
PRIV_REQUIRES bootloader_support
PRIV_REQUIRES bootloader_support json
)
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--undefined=esp_app_desc")

View File

@@ -3,9 +3,10 @@
#include "application_name.h"
#include "esp_err.h"
#include "esp_app_format.h"
#include "cJSON.h"
#include "stdbool.h"
extern esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length);
extern cJSON * gpio_list;
const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
.magic_word = ESP_APP_DESC_MAGIC_WORD,
.version = PROJECT_VER,
@@ -26,6 +27,14 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
.date = "",
#endif
};
cJSON * get_gpio_list(bool refresh){
if(!gpio_list){
gpio_list = cJSON_CreateArray();
}
return gpio_list;
}
void register_optional_cmd(void) {
}
int main(int argc, char **argv){
return 1;

View File

@@ -40,6 +40,29 @@ const __attribute__((section(".rodata_desc"))) esp_app_desc_t esp_app_desc = {
#endif
};
extern void register_audio_config(void);
extern void register_rotary_config(void);
extern void register_ledvu_config(void);
extern void register_nvs();
extern cJSON * get_gpio_list_handler(bool refresh);
void register_optional_cmd(void) {
#if CONFIG_WITH_CONFIG_UI
register_rotary_config();
#endif
register_audio_config();
register_ledvu_config();
register_nvs();
}
cJSON * get_gpio_list(bool refresh){
#if CONFIG_WITH_CONFIG_UI
return get_gpio_list_handler(refresh);
#else
return cJSON_CreateArray();
#endif
}
extern int squeezelite_main(int argc, char **argv);
static int launchsqueezelite(int argc, char **argv);

View File

@@ -20,7 +20,10 @@
#include "tools.h"
#include "cJSON.h"
#include "cmd_i2ctools.h"
#if defined(CONFIG_WITH_METRICS)
#include "metrics.h"
#endif
#include "cmd_system.h"
const char * desc_squeezelite ="Squeezelite Options";
const char * desc_dac= "DAC Options";
const char * desc_cspotc= "Spotify (cSpot) Options";
@@ -32,6 +35,8 @@ const char * desc_rotary= "Rotary Control";
const char * desc_ledvu= "Led Strip Options";
extern const struct adac_s *dac_set[];
extern void equalizer_set_loudness(uint8_t);
extern void register_optional_cmd(void);
#define CODECS_BASE "flac|pcm|mp3|ogg"
#if NO_FAAD
@@ -129,6 +134,7 @@ static struct {
struct arg_str *deviceName;
// struct arg_int *volume;
struct arg_int *bitrate;
struct arg_int *zeroConf;
struct arg_end *end;
} cspot_args;
static struct {
@@ -140,6 +146,7 @@ static struct {
} spdif_args;
static struct {
struct arg_str *jack_behavior;
struct arg_int *loudness;
struct arg_end *end;
} audio_args;
static struct {
@@ -326,9 +333,8 @@ static int do_bt_source_cmd(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
// char value[100] ={0};
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -437,9 +443,8 @@ static int do_audio_cmd(int argc, char **argv){
int nerrors = arg_parse(argc, argv,(void **)&audio_args);
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -447,6 +452,30 @@ static int do_audio_cmd(int argc, char **argv){
fclose(f);
return 1;
}
err = ESP_OK; // suppress any error code that might have happened in a previous step
if(audio_args.loudness->count>0){
char p[4]={0};
int loudness_val = audio_args.loudness->ival[0];
if( loudness_val < 0 || loudness_val>10){
nerrors++;
fprintf(f,"Invalid loudness value %d. Valid values are between 0 and 10.\n",loudness_val);
}
// it's not necessary to store loudness in NVS as set_loudness does it, but it does not hurt
else {
itoa(loudness_val,p,10);
err = config_set_value(NVS_TYPE_STR, "loudness", p);
}
if(err!=ESP_OK){
nerrors++;
fprintf(f,"Error setting Loudness value %s. %s\n",p, esp_err_to_name(err));
}
else {
fprintf(f,"Loudness changed to %s\n",p);
equalizer_set_loudness(loudness_val);
}
}
if(audio_args.jack_behavior->count>0){
err = ESP_OK; // suppress any error code that might have happened in a previous step
@@ -501,9 +530,8 @@ static int do_spdif_cmd(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -540,9 +568,8 @@ static int do_rotary_cmd(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -612,9 +639,8 @@ static int do_cspot_config(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
return 1;
}
@@ -629,6 +655,9 @@ static int do_cspot_config(int argc, char **argv){
if(cspot_args.bitrate->count>0){
cjson_update_number(&cspot_config,cspot_args.bitrate->hdr.longopts,cspot_args.bitrate->ival[0]);
}
if(cspot_args.zeroConf->count>0){
cjson_update_number(&cspot_config,cspot_args.zeroConf->hdr.longopts,cspot_args.zeroConf->ival[0]);
}
if(!nerrors ){
fprintf(f,"Storing cspot parameters.\n");
@@ -641,6 +670,9 @@ static int do_cspot_config(int argc, char **argv){
if(cspot_args.bitrate->count>0){
fprintf(f,"Bitrate changed to %u\n",cspot_args.bitrate->ival[0]);
}
if(cspot_args.zeroConf->count>0){
fprintf(f,"ZeroConf changed to %u\n",cspot_args.zeroConf->ival[0]);
}
}
if(!nerrors ){
fprintf(f,"Done.\n");
@@ -665,9 +697,8 @@ static int do_ledvu_cmd(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -712,7 +743,7 @@ static int do_i2s_cmd(int argc, char **argv)
cmd_send_messaging(argv[0],MESSAGING_ERROR,"DAC Configuration is locked on this platform\n");
return 1;
}
strcpy(i2s_dac_pin.model, "I2S");
ESP_LOGD(TAG,"Processing i2s command %s with %d parameters",argv[0],argc);
esp_err_t err=ESP_OK;
@@ -725,10 +756,8 @@ static int do_i2s_cmd(int argc, char **argv)
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
ESP_LOGE(TAG, "do_i2s_cmd: Failed to open memstream");
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -826,6 +855,10 @@ cJSON * cspot_cb(){
if(cspot_values){
cJSON_AddNumberToObject(values,cspot_args.bitrate->hdr.longopts,cJSON_GetNumberValue(cspot_values));
}
cspot_values = cJSON_GetObjectItem(cspot_config,cspot_args.zeroConf->hdr.longopts);
if(cspot_values){
cJSON_AddNumberToObject(values,cspot_args.zeroConf->hdr.longopts,cJSON_GetNumberValue(cspot_values));
}
cJSON_Delete(cspot_config);
return values;
@@ -835,7 +868,9 @@ cJSON * i2s_cb(){
cJSON * values = cJSON_CreateObject();
const i2s_platform_config_t * i2s_conf= config_dac_get( );
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("i2s",i2s_conf->pin.data_out_num>=0);
#endif
if(i2s_conf->pin.bck_io_num>0 ) {
cJSON_AddNumberToObject(values,i2s_args.clock->hdr.longopts,i2s_conf->pin.bck_io_num);
}
@@ -872,6 +907,11 @@ cJSON * i2s_cb(){
cJSON * spdif_cb(){
cJSON * values = cJSON_CreateObject();
const i2s_platform_config_t * spdif_conf= config_spdif_get( );
if(spdif_conf->pin.data_out_num>=0) {
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("spdif","enabled");
#endif
}
if(spdif_conf->pin.bck_io_num>0 ) {
cJSON_AddNumberToObject(values,"clock",spdif_conf->pin.bck_io_num);
}
@@ -890,7 +930,9 @@ cJSON * rotary_cb(){
bool raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
free(p);
const rotary_struct_t *rotary= config_rotary_get();
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("rotary",GPIO_IS_VALID_GPIO(rotary->A ));
#endif
if(GPIO_IS_VALID_GPIO(rotary->A ) && rotary->A>=0 && GPIO_IS_VALID_GPIO(rotary->B) && rotary->B>=0){
cJSON_AddNumberToObject(values,rotary_args.A->hdr.longopts,rotary->A);
cJSON_AddNumberToObject(values,rotary_args.B->hdr.longopts,rotary->B);
@@ -909,7 +951,11 @@ cJSON * rotary_cb(){
cJSON * ledvu_cb(){
cJSON * values = cJSON_CreateObject();
const ledvu_struct_t *ledvu= config_ledvu_get();
if(GPIO_IS_VALID_GPIO(ledvu->gpio )){
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("led_vu","enabled");
#endif
}
if(GPIO_IS_VALID_GPIO(ledvu->gpio) && ledvu->gpio>=0 && ledvu->length > 0){
cJSON_AddNumberToObject(values,"gpio",ledvu->gpio);
cJSON_AddNumberToObject(values,"length",ledvu->length);
@@ -927,7 +973,16 @@ cJSON * audio_cb(){
cJSON * values = cJSON_CreateObject();
char * p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
cJSON_AddStringToObject(values,"jack_behavior",(strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0)?"Headphones":"Subwoofer");
FREE_AND_NULL(p);
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("jack_mute",atoi(p)>=0);
#endif
FREE_AND_NULL(p);
p = config_alloc_get_default(NVS_TYPE_STR, "loudness", "0", 0);
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("loudness",atoi(p)>=0);
#endif
cJSON_AddStringToObject(values,"loudness",p);
FREE_AND_NULL(p);
return values;
}
cJSON * bt_source_cb(){
@@ -935,6 +990,9 @@ cJSON * bt_source_cb(){
char * p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_sink_name", NULL, 0);
if(p){
cJSON_AddStringToObject(values,"sink_name",p);
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("btsource",strlen(p)>0);
#endif
}
FREE_AND_NULL(p);
// p = config_alloc_get_default(NVS_TYPE_STR, "a2dp_ctmt", NULL, 0);
@@ -985,9 +1043,8 @@ static int do_squeezelite_cmd(int argc, char **argv)
int nerrors = arg_parse_msg(argc, argv,(struct arg_hdr ** )&squeezelite_args);
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
fprintf(f,"Not yet implemented!");
@@ -1006,59 +1063,62 @@ cJSON * squeezelite_cb(){
char *buf = NULL;
size_t buf_size = 0;
int nerrors=1;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
log_send_messaging(MESSAGING_ERROR,"Unable to parse squeezelite parameters");
return values;
}
if(nvs_config && strlen(nvs_config)>0){
ESP_LOGD(TAG,"Parsing command %s",nvs_config);
argv = (char **) calloc(22, sizeof(char *));
if (argv == NULL) {
FREE_AND_NULL(nvs_config);
fclose(f);
return values;
}
size_t argc = esp_console_split_argv(nvs_config, argv,22);
if (argc != 0) {
nerrors = arg_parse(argc, argv,(void **)&squeezelite_args);
ESP_LOGD(TAG,"Parsing completed");
}
}
if (nerrors == 0) {
get_str_parm_json(squeezelite_args.buffers, values);
get_str_parm_json(squeezelite_args.codecs, values);
get_lit_parm_json(squeezelite_args.header_format, values);
get_str_parm_json(squeezelite_args.log_level, values);
// get_str_parm_json(squeezelite_args.log_level_all, values);
// get_str_parm_json(squeezelite_args.log_level_decode, values);
// get_str_parm_json(squeezelite_args.log_level_output, values);
// get_str_parm_json(squeezelite_args.log_level_slimproto, values);
// get_str_parm_json(squeezelite_args.log_level_stream, values);
get_str_parm_json(squeezelite_args.mac_addr, values);
get_str_parm_json(squeezelite_args.output_device, values);
#if defined(CONFIG_WITH_METRICS)
if(squeezelite_args.output_device->sval[0]!=NULL && strlen(squeezelite_args.output_device->sval[0])>0){
metrics_add_feature_variant("output",squeezelite_args.output_device->sval[0]);
}
#endif
get_str_parm_json(squeezelite_args.model_name, values);
get_str_parm_json(squeezelite_args.name, values);
get_int_parm_json(squeezelite_args.rate, values);
get_str_parm_json(squeezelite_args.rates, values);
get_str_parm_json(squeezelite_args.server, values);
get_int_parm_json(squeezelite_args.timeout, values);
char * p = cJSON_Print(values);
ESP_LOGD(TAG,"%s",p);
free(p);
}
else {
if(nvs_config && strlen(nvs_config)>0){
ESP_LOGD(TAG,"Parsing command %s",nvs_config);
argv = (char **) calloc(22, sizeof(char *));
if (argv == NULL) {
FREE_AND_NULL(nvs_config);
fclose(f);
return values;
}
size_t argc = esp_console_split_argv(nvs_config, argv,22);
if (argc != 0) {
nerrors = arg_parse(argc, argv,(void **)&squeezelite_args);
ESP_LOGD(TAG,"Parsing completed");
}
}
if (nerrors == 0) {
get_str_parm_json(squeezelite_args.buffers, values);
get_str_parm_json(squeezelite_args.codecs, values);
get_lit_parm_json(squeezelite_args.header_format, values);
get_str_parm_json(squeezelite_args.log_level, values);
// get_str_parm_json(squeezelite_args.log_level_all, values);
// get_str_parm_json(squeezelite_args.log_level_decode, values);
// get_str_parm_json(squeezelite_args.log_level_output, values);
// get_str_parm_json(squeezelite_args.log_level_slimproto, values);
// get_str_parm_json(squeezelite_args.log_level_stream, values);
get_str_parm_json(squeezelite_args.mac_addr, values);
get_str_parm_json(squeezelite_args.output_device, values);
get_str_parm_json(squeezelite_args.model_name, values);
get_str_parm_json(squeezelite_args.name, values);
get_int_parm_json(squeezelite_args.rate, values);
get_str_parm_json(squeezelite_args.rates, values);
get_str_parm_json(squeezelite_args.server, values);
get_int_parm_json(squeezelite_args.timeout, values);
char * p = cJSON_Print(values);
ESP_LOGD(TAG,"%s",p);
free(p);
}
else {
arg_print_errors(f, squeezelite_args.end, desc_squeezelite);
}
fflush (f);
if(strlen(buf)>0){
log_send_messaging(nerrors?MESSAGING_ERROR:MESSAGING_INFO,"%s", buf);
}
fclose(f);
FREE_AND_NULL(buf);
arg_print_errors(f, squeezelite_args.end, desc_squeezelite);
}
fflush (f);
if(strlen(buf)>0){
log_send_messaging(nerrors?MESSAGING_ERROR:MESSAGING_INFO,"%s", buf);
}
fclose(f);
FREE_AND_NULL(buf);
FREE_AND_NULL(nvs_config);
FREE_AND_NULL(argv);
return values;
@@ -1076,7 +1136,7 @@ static char * get_log_level_options(const char * longname){
// loop through dac_set and concatenate model name separated with |
static char * get_dac_list(){
const char * ES8388_MODEL_NAME = "ES8388|";
const char * EXTRA_MODEL_NAMES = "ES8388|I2S";
char * dac_list=NULL;
size_t total_len=0;
for(int i=0;dac_set[i];i++){
@@ -1087,7 +1147,7 @@ static char * get_dac_list(){
break;
}
}
total_len+=strlen(ES8388_MODEL_NAME);
total_len+=strlen(EXTRA_MODEL_NAMES);
dac_list = malloc_init_external(total_len+1);
if(dac_list){
for(int i=0;dac_set[i];i++){
@@ -1099,7 +1159,7 @@ static char * get_dac_list(){
break;
}
}
strcat(dac_list,ES8388_MODEL_NAME);
strcat(dac_list,EXTRA_MODEL_NAMES);
}
return dac_list;
}
@@ -1171,9 +1231,8 @@ static int do_register_known_templates_config(int argc, char **argv){
char *buf = NULL;
size_t buf_size = 0;
cJSON * config_name =NULL;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.\n");
return 1;
}
if(nerrors >0){
@@ -1256,6 +1315,7 @@ static void register_known_templates_config(){
static void register_cspot_config(){
cspot_args.deviceName = arg_str1(NULL,"deviceName","","Device Name");
cspot_args.bitrate = arg_int1(NULL,"bitrate","96|160|320","Streaming Bitrate (kbps)");
cspot_args.zeroConf = arg_int1(NULL,"zeroConf","0|1","Force use of ZeroConf");
// cspot_args.volume = arg_int1(NULL,"volume","","Spotify Volume");
cspot_args.end = arg_end(1);
const esp_console_cmd_t cmd = {
@@ -1270,7 +1330,7 @@ static void register_cspot_config(){
}
#endif
static void register_i2s_config(void){
i2s_args.model_name = arg_str1(NULL,"model_name",STR_OR_BLANK(get_dac_list()),"DAC Model Name");
i2s_args.model_name = arg_str0(NULL,"model_name",STR_OR_BLANK(get_dac_list()),"DAC Model Name");
i2s_args.clear = arg_lit0(NULL, "clear", "Clear configuration");
i2s_args.clock = arg_int0(NULL,"clock","<n>","Clock GPIO. e.g. 33");
i2s_args.wordselect = arg_int0(NULL,"wordselect","<n>","Word Select GPIO. e.g. 25");
@@ -1311,7 +1371,7 @@ static void register_bt_source_config(void){
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
static void register_rotary_config(void){
void register_rotary_config(void){
rotary_args.rem = arg_rem("remark","One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.\r\nEncoder is normally hard-coded to respectively knob left, right and push on LMS and to volume down/up/play toggle on BT and AirPlay.");
rotary_args.A = arg_int1(NULL,"A","gpio","A/DT gpio");
rotary_args.B = arg_int1(NULL,"B","gpio","B/CLK gpio");
@@ -1334,7 +1394,7 @@ static void register_rotary_config(void){
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
static void register_ledvu_config(void){
void register_ledvu_config(void){
ledvu_args.type = arg_str1(NULL,"type","<none>|WS2812","Led type (supports one rgb strip to display built in effects and allow remote control through 'dmx' messaging)");
ledvu_args.length = arg_int1(NULL,"length","<1..255>","Strip length (1-255 supported)");
ledvu_args.gpio = arg_int1(NULL,"gpio","gpio","Data pin");
@@ -1352,8 +1412,10 @@ static void register_ledvu_config(void){
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
static void register_audio_config(void){
void register_audio_config(void){
audio_args.jack_behavior = arg_str0("j", "jack_behavior","Headphones|Subwoofer","On supported DAC, determines the audio jack behavior. Selecting headphones will cause the external amp to be muted on insert, while selecting Subwoofer will keep the amp active all the time.");
audio_args.loudness = arg_int0("l", "loudness","0-10","Sets a loudness level, from 0 to 10. 0 will disable the loudness completely. Note that LMS has priority over setting this value, so use it only when away from your server.");
audio_args.end = arg_end(6);
audio_args.end = arg_end(6);
const esp_console_cmd_t cmd = {
.command = CFG_TYPE_AUDIO("general"),
@@ -1424,25 +1486,36 @@ static void register_squeezelite_config(void){
cmd_to_json_with_cb(&cmd,&squeezelite_cb);
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
void dummy_register_cmd(){
}
void register_config_cmd(void){
if(!is_dac_config_locked()){
register_known_templates_config();
register_known_templates_config();
}
#ifdef CONFIG_CSPOT_SINK
register_cspot_config();
#endif
register_audio_config();
// register_squeezelite_config();
register_bt_source_config();
#if CONFIG_WITH_CONFIG_UI
if(!is_dac_config_locked()){
register_i2s_config();
}
else {
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("i2s",true);
#endif
}
if(!is_spdif_config_locked()){
register_spdif_config();
}
register_rotary_config();
register_ledvu_config();
else {
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature("spdif",true);
#endif
}
#endif
register_optional_cmd();
}

File diff suppressed because it is too large Load Diff

View File

@@ -589,6 +589,7 @@ void register_nvs()
.func = &list_entries,
.argtable = &list_args
};
MEMTRACE_PRINT_DELTA_MESSAGE("registering list_entries_cmd");
ESP_ERROR_CHECK(esp_console_cmd_register(&list_entries_cmd));
MEMTRACE_PRINT_DELTA_MESSAGE("registering set_cmd");

View File

@@ -62,7 +62,7 @@ static int perform_ota_update(int argc, char **argv)
const esp_console_cmd_t cmd = {
.command = "update",
.help = "Updates the application binary from the provided URL",
.help = "Update from URL",
.hint = NULL,
.func = &perform_ota_update,
.argtable = &ota_args

View File

@@ -31,6 +31,9 @@
#include "messaging.h"
#include "platform_console.h"
#include "tools.h"
#if defined(CONFIG_WITH_METRICS)
#include "Metrics.h"
#endif
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
#pragma message("Runtime stats enabled")
@@ -64,8 +67,10 @@ static void register_heap();
static void register_dump_heap();
static void register_version();
static void register_restart();
#if CONFIG_WITH_CONFIG_UI
static void register_deep_sleep();
static void register_light_sleep();
#endif
static void register_factory_boot();
static void register_restart_ota();
static void register_set_services();
@@ -73,24 +78,42 @@ static void register_set_services();
static void register_tasks();
#endif
extern BaseType_t network_manager_task;
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size){
FILE *f = open_memstream(buf, buf_size);
if (f == NULL) {
cmd_send_messaging(cmdname,MESSAGING_ERROR,"Unable to open memory stream.");
}
return f;
}
void register_system()
{
register_free();
register_set_services();
register_setdevicename();
register_free();
register_heap();
register_dump_heap();
register_setdevicename();
register_version();
register_restart();
register_deep_sleep();
register_light_sleep();
register_factory_boot();
register_restart_ota();
#if WITH_TASKS_INFO
register_tasks();
#endif
#if CONFIG_WITH_CONFIG_UI
register_deep_sleep();
register_light_sleep();
#endif
}
void simple_restart()
{
log_send_messaging(MESSAGING_WARNING,"Rebooting.");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
/* 'version' command */
static int get_version(int argc, char **argv)
{
@@ -128,36 +151,23 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
{
if(is_recovery_running){
if(partition_subtype ==ESP_PARTITION_SUBTYPE_APP_FACTORY){
log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
return ESP_OK;
// log_send_messaging(MESSAGING_WARNING,"RECOVERY application is already active");
simple_restart();
}
}
else {
if(partition_subtype !=ESP_PARTITION_SUBTYPE_APP_FACTORY){
log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
return ESP_OK;
// log_send_messaging(MESSAGING_WARNING,"SQUEEZELITE application is already active");
simple_restart();
}
}
esp_err_t err = ESP_OK;
bool bFound=false;
log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
// log_send_messaging(MESSAGING_INFO, "Looking for partition type %u",partition_subtype);
const esp_partition_t *partition;
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
if(it == NULL){
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Cannot iterate through partitions");
log_send_messaging(MESSAGING_ERROR,"Reboot failed. Partitions error");
}
else
{
@@ -166,15 +176,11 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
ESP_LOGD(TAG, "Releasing partition iterator");
esp_partition_iterator_release(it);
if(partition != NULL){
log_send_messaging(MESSAGING_INFO, "Found application partition %s sub type %u", partition->label,partition_subtype);
log_send_messaging(MESSAGING_INFO, "Rebooting to %s", partition->label);
err=esp_ota_set_boot_partition(partition);
if(err!=ESP_OK){
bFound=false;
log_send_messaging(MESSAGING_ERROR,"Unable to select partition for reboot: %s",esp_err_to_name(err));
}
else{
bFound=true;
}
}
else
{
@@ -183,13 +189,7 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
}
ESP_LOGD(TAG, "Yielding to other processes");
taskYIELD();
if(bFound) {
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration changes. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
simple_restart();
}
return ESP_OK;
@@ -197,46 +197,31 @@ esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
static int restart(int argc, char **argv)
{
log_send_messaging(MESSAGING_WARNING, "\n\nPerforming a simple restart to the currently active partition.");
if(!wait_for_commit()){
cmd_send_messaging(argv[0],MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
simple_restart();
return 0;
}
void simple_restart()
{
log_send_messaging(MESSAGING_WARNING,"System reboot requested.");
if(!wait_for_commit()){
log_send_messaging(MESSAGING_WARNING,"Unable to commit configuration. ");
}
vTaskDelay(750/ portTICK_PERIOD_MS);
esp_restart();
}
esp_err_t guided_restart_ota(){
log_send_messaging(MESSAGING_WARNING,"System reboot to Application requested");
log_send_messaging(MESSAGING_WARNING,"Booting to Squeezelite");
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
return ESP_FAIL; // return fail. This should never return... we're rebooting!
}
esp_err_t guided_factory(){
log_send_messaging(MESSAGING_WARNING,"System reboot to recovery requested");
log_send_messaging(MESSAGING_WARNING,"Booting to recovery");
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
return ESP_FAIL; // return fail. This should never return... we're rebooting!
}
static int restart_factory(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into recovery");
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Recovery");
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
return 0; // return fail. This should never return... we're rebooting!
}
static int restart_ota(int argc, char **argv)
{
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Executing guided boot into ota app 0");
cmd_send_messaging(argv[0],MESSAGING_WARNING, "Booting to Squeezelite");
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
return 0; // return fail. This should never return... we're rebooting!
}
@@ -248,7 +233,9 @@ static void register_restart()
.hint = NULL,
.func = &restart,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
static void register_restart_ota()
@@ -259,7 +246,9 @@ static void register_restart_ota()
.hint = NULL,
.func = &restart_ota,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
@@ -271,7 +260,9 @@ static void register_factory_boot()
.hint = NULL,
.func = &restart_factory,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
/** 'free' command prints available heap memory */
@@ -287,11 +278,14 @@ static void register_free()
{
const esp_console_cmd_t cmd = {
.command = "free",
.help = "Get the current size of free heap memory",
.help = "Get free heap memory",
.hint = NULL,
.func = &free_mem,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
static int dump_heap(int argc, char **argv)
@@ -303,16 +297,16 @@ static int dump_heap(int argc, char **argv)
/* 'heap' command prints minumum heap size */
static int heap_size(int argc, char **argv)
{
ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
heap_caps_get_free_size(MALLOC_CAP_DMA),
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
// ESP_LOGI(TAG,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
// heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
// heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
// heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL),
// heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
// heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
// heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM),
// heap_caps_get_free_size(MALLOC_CAP_DMA),
// heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
// heap_caps_get_largest_free_block(MALLOC_CAP_DMA));
cmd_send_messaging(argv[0],MESSAGING_INFO,"Heap internal:%zu (min:%zu) (largest block:%zu)\nexternal:%zu (min:%zu) (largest block:%zd)\ndma :%zu (min:%zu) (largest block:%zd)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
@@ -457,9 +451,8 @@ static int setdevicename(int argc, char **argv)
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
return 1;
}
nerrors+=setnamevar("a2dp_dev_name", f, name);
@@ -488,11 +481,13 @@ static void register_heap()
{
const esp_console_cmd_t heap_cmd = {
.command = "heap",
.help = "Get minimum size of free heap memory found during execution",
.help = "Get minimum size of free heap memory",
.hint = NULL,
.func = &heap_size,
};
#if CONFIG_WITH_CONFIG_UI
cmd_to_json(&heap_cmd);
#endif
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
}
@@ -521,6 +516,7 @@ static void register_setdevicename()
.func = &setdevicename,
.argtable = &name_args
};
cmd_to_json_with_cb(&set_name,&setdevicename_cb);
ESP_ERROR_CHECK(esp_console_cmd_register(&set_name));
}
@@ -562,6 +558,7 @@ static void register_tasks()
/** 'deep_sleep' command puts the chip into deep sleep mode */
#if CONFIG_WITH_CONFIG_UI
static struct {
struct arg_int *wakeup_time;
struct arg_int *wakeup_gpio_num;
@@ -618,15 +615,15 @@ static void register_deep_sleep()
const esp_console_cmd_t cmd = {
.command = "deep_sleep",
.help = "Enter deep sleep mode. "
"Two wakeup modes are supported: timer and GPIO. "
"If no wakeup option is specified, will sleep indefinitely.",
.help = "Enter deep sleep mode. ",
.hint = NULL,
.func = &deep_sleep,
.argtable = &deep_sleep_args
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#endif
static int enable_disable(FILE * f,char * nvs_name, struct arg_lit *arg){
esp_err_t err = config_set_value(NVS_TYPE_STR, nvs_name, arg->count>0?"Y":"N");
const char * name = arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary;
@@ -649,9 +646,8 @@ static int do_set_services(int argc, char **argv)
}
char *buf = NULL;
size_t buf_size = 0;
FILE *f = open_memstream(&buf, &buf_size);
FILE *f = system_open_memstream(argv[0],&buf, &buf_size);
if (f == NULL) {
cmd_send_messaging(argv[0],MESSAGING_ERROR,"Unable to open memory stream.");
return 1;
}
@@ -674,7 +670,7 @@ static int do_set_services(int argc, char **argv)
if(err!=ESP_OK){
nerrors++;
fprintf(f,"Error setting telnet service to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
fprintf(f,"Error setting telnet to %s. %s\n",set_services_args.telnet->sval[0], esp_err_to_name(err));
}
else {
fprintf(f,"Telnet service changed to %s\n",set_services_args.telnet->sval[0]);
@@ -706,7 +702,6 @@ cJSON * set_services_cb(){
#if WITH_TASKS_INFO
console_set_bool_parameter(values,"stats",set_services_args.stats);
#endif
if ((p = config_alloc_get(NVS_TYPE_STR, "telnet_enable")) != NULL) {
if(strcasestr("YX",p)!=NULL){
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Telnet Only");
@@ -717,7 +712,9 @@ cJSON * set_services_cb(){
else {
cJSON_AddStringToObject(values,set_services_args.telnet->hdr.longopts,"Disabled");
}
#if defined(CONFIG_WITH_METRICS)
metrics_add_feature_variant("telnet",p);
#endif
FREE_AND_NULL(p);
}
@@ -745,6 +742,8 @@ static void register_set_services(){
cmd_to_json_with_cb(&cmd,&set_services_cb);
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#if CONFIG_WITH_CONFIG_UI
static struct {
struct arg_int *wakeup_time;
struct arg_int *wakeup_gpio_num;
@@ -836,4 +835,4 @@ static void register_light_sleep()
};
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
}
#endif

View File

@@ -17,7 +17,7 @@ void register_system();
esp_err_t guided_factory();
esp_err_t guided_restart_ota();
void simple_restart();
FILE * system_open_memstream(const char * cmdname,char **buf,size_t *buf_size);
#ifdef __cplusplus
}
#endif

View File

@@ -37,6 +37,9 @@ extern bool bypass_network_manager;
#define JOIN_TIMEOUT_MS (10000)
#include "platform_console.h"
// To enable wifi configuration from the command line, uncomment the line below
// define WIFI_CMDLINE 1
extern EventGroupHandle_t network_event_group;
extern const int CONNECTED_BIT;
@@ -53,13 +56,6 @@ static struct {
// todo: implement access point config - cmd_to_json(&i2cdetect_cmd);
///** Arguments used by 'join' function */
//static struct {
// struct arg_int *autoconnect;
// struct arg_end *end;
//} auto_connect_args;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
@@ -72,27 +68,7 @@ static void event_handler(void* arg, esp_event_base_t event_base,
xEventGroupSetBits(network_event_group, CONNECTED_BIT);
}
}
//bool wait_for_wifi(){
//
// bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
//
// if(!connected){
// ESP_LOGD(TAG,"Waiting for WiFi...");
// connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
// pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
// if(!connected){
// ESP_LOGD(TAG,"wifi timeout.");
// }
// else
// {
// ESP_LOGI(TAG,"WiFi Connected!");
// }
// }
//
//
// return connected;
//
//}
static void initialise_wifi(void)
{
static bool initialized = false;
@@ -204,8 +180,10 @@ void register_wifi_join()
void register_wifi()
{
#ifdef WIFI_CMDLINE
register_wifi_join();
if(bypass_network_manager){
initialise_wifi();
}
#endif
}

View File

@@ -27,7 +27,9 @@
#include "platform_config.h"
#include "telnet.h"
#include "tools.h"
#if defined(CONFIG_WITH_METRICS)
#include "metrics.h"
#endif
#include "messaging.h"
#include "config.h"
@@ -85,14 +87,22 @@ cJSON * get_cmd_list(){
}
void console_set_bool_parameter(cJSON * root,char * nvs_name, struct arg_lit *arg){
char * p=NULL;
if(!root) {
bool enabled = false;
if(!root) {
ESP_LOGE(TAG,"Invalid json parameter. Cannot set %s from %s",arg->hdr.longopts?arg->hdr.longopts:arg->hdr.glossary,nvs_name);
return;
}
if ((p = config_alloc_get(NVS_TYPE_STR, nvs_name)) != NULL) {
cJSON_AddBoolToObject(root,arg->hdr.longopts,strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0);
enabled = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
cJSON_AddBoolToObject(root,arg->hdr.longopts,enabled);
FREE_AND_NULL(p);
}
#if defined(CONFIG_WITH_METRICS)
if(enabled){
metrics_add_feature(nvs_name,"enabled");
}
#endif
}
struct arg_end *getParmsEnd(struct arg_hdr * * argtable){
if(!argtable) return NULL;
@@ -360,8 +370,6 @@ void console_start() {
register_system();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering config commands");
register_config_cmd();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering nvs commands");
register_nvs();
MEMTRACE_PRINT_DELTA_MESSAGE("Registering wifi commands");
register_wifi();

View File

@@ -34,7 +34,7 @@
#include "log_util.h"
#define RTSP_STACK_SIZE (8*1024)
#define SEARCH_STACK_SIZE (3*1048)
#define SEARCH_STACK_SIZE (3*1024)
typedef struct raop_ctx_s {
#ifdef WIN32
@@ -276,6 +276,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
free(ctx);
}
/*----------------------------------------------------------------------------*/
bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
struct sockaddr_in addr;
@@ -325,7 +326,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
break;
}
default:
break;
break;
}
// no command to send to remote or no remote found yet
@@ -348,16 +349,19 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
asprintf(&method, "GET /ctrl-int/1/%s HTTP/1.0", command);
kd_add(headers, "Active-Remote", ctx->active_remote.id);
kd_add(headers, "Connection", "close");
kd_add(headers, "Connection", "close");
buf = http_send(sock, method, headers);
len = recv(sock, resp, 512, 0);
if (len > 0) resp[len-1] = '\0';
if (len > 0) resp[len-1] = '\0';
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
NFREE(method);
NFREE(buf);
kd_free(headers);
success = true;
} else {
kd_free(headers);
LOG_INFO("[%p]: can't connect to remote for %s", ctx, command);
}
free(command);
@@ -622,15 +626,19 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
struct metadata_s metadata;
dmap_settings settings = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, on_dmap_string, NULL,
NULL
};
NULL
};
settings.ctx = &metadata;
memset(&metadata, 0, sizeof(struct metadata_s));
settings.ctx = &metadata;
if (!dmap_parse(&settings, body, len)) {
uint32_t timestamp = 0;
if ((p = kd_lookup(headers, "RTP-Info")) != NULL) sscanf(p, "%*[^=]=%d", &timestamp);
LOG_INFO("[%p]: received metadata (ts: %d)\n\tartist: %s\n\talbum: %s\n\ttitle: %s",
ctx, metadata.artist ? metadata.artist : "", metadata.album ? metadata.album : "",
metadata.title ? metadata.title : "");
ctx, timestamp, metadata.artist ? metadata.artist : "", metadata.album ? metadata.album : "",
metadata.title ? metadata.title : "");
success = ctx->cmd_cb(RAOP_METADATA, metadata.artist, metadata.album, metadata.title, timestamp);
free_metadata(&metadata);
}
} else if (body && ((p = kd_lookup(headers, "Content-Type")) != NULL) && strcasestr(p, "image/jpeg")) {
uint32_t timestamp = 0;
@@ -680,7 +688,7 @@ void cleanup_rtsp(raop_ctx_t *ctx, bool abort) {
#ifdef WIN32
pthread_join(ctx->active_remote.thread, NULL);
close_mDNS(ctx->active_remote.handle);
#else
#else
// need to make sure no search is on-going and reclaim task memory
ctx->active_remote.running = false;
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);

View File

@@ -113,13 +113,19 @@ static bool cmd_handler(raop_event_t event, ...) {
case RAOP_SETUP:
actrls_set(controls, false, NULL, actrls_ir_action);
displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY", true);
displayer_artwork(NULL);
break;
case RAOP_PLAY:
displayer_control(DISPLAYER_TIMER_RUN);
break;
case RAOP_FLUSH:
displayer_control(DISPLAYER_TIMER_PAUSE);
break;
break;
case RAOP_STALLED:
raop_abort(raop);
actrls_unset();
displayer_control(DISPLAYER_SHUTDOWN);
break;
case RAOP_STOP:
actrls_unset();
displayer_control(DISPLAYER_SUSPEND);
@@ -127,7 +133,6 @@ static bool cmd_handler(raop_event_t event, ...) {
case RAOP_METADATA: {
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
displayer_metadata(artist, album, title);
displayer_artwork(NULL);
break;
}
case RAOP_ARTWORK: {
@@ -170,7 +175,7 @@ static void raop_sink_start(nm_state_t state_id, int sub_state) {
esp_netif_get_mac(netif, mac);
cmd_handler_chain = raop_cbs.cmd;
LOG_INFO( "Starting Airplay for ip %s with servicename %s", inet_ntoa(ipInfo.ip.addr), sink_name);
LOG_INFO( "starting Airplay for ip %s with servicename %s", inet_ntoa(ipInfo.ip.addr), sink_name);
raop = raop_create(ipInfo.ip.addr, sink_name, mac, 0, cmd_handler, raop_cbs.data);
free(sink_name);
}
@@ -191,8 +196,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
*/
void raop_disconnect(void) {
LOG_INFO("forced disconnection");
displayer_control(DISPLAYER_SHUTDOWN);
// in case we can't communicate with AirPlay controller, abort session
if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop);
actrls_unset();
if (!raop_cmd(raop, RAOP_STOP, NULL)) cmd_handler(RAOP_STALLED);
else displayer_control(DISPLAYER_SHUTDOWN);
}

View File

@@ -14,7 +14,7 @@
#define RAOP_SAMPLE_RATE 44100
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP,
typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, RAOP_STALLED,
RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD,
RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ;

View File

@@ -82,7 +82,7 @@ static log_level *loglevel = &raop_loglevel;
#define RTP_SYNC (0x01)
#define NTP_SYNC (0x02)
#define RESEND_TO 200
#define RESEND_TO 250
enum { DATA = 0, CONTROL, TIMING };
@@ -149,6 +149,7 @@ typedef struct rtp_s {
struct alac_codec_s *alac_codec;
int flush_seqno;
bool playing;
int stalled;
raop_data_cb_t data_cb;
raop_cmd_cb_t cmd_cb;
} rtp_t;
@@ -466,26 +467,27 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->ab_write = seqno;
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
} else if (seq_order(ctx->ab_write, seqno)) {
seq_t i;
u32_t now;
// newer than expected
if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) {
// only get rtp latency-1 frames back (last one is seqno)
LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
ctx->ab_write = seqno - ctx->latency / ctx->frame_size;
}
// this is a shitstorm, reset buffer
LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read);
ctx->ab_read = seqno;
} else {
// request re-send missed frames and evaluate resent date as a whole *after*
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
// resend date is after all requests have been sent
u32_t now = gettime_ms();
// set expected timing of missed frames for buffer_push_packet and set last_resend date
for (seq_t i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
ctx->audio_buffer[BUFIDX(i)].last_resend = now;
}
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
}
// need to request re-send and adjust timing of gaps
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) {
ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size;
ctx->audio_buffer[BUFIDX(i)].last_resend = now;
}
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
abuf = ctx->audio_buffer + BUFIDX(seqno);
ctx->ab_write = seqno;
} else if (seq_order(ctx->ab_read, seqno + 1)) {
@@ -524,10 +526,9 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
static void buffer_push_packet(rtp_t *ctx) {
abuf_t *curframe = NULL;
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
int i;
// not ready to play yet
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
// there is always at least one frame in the buffer
do {
@@ -573,10 +574,11 @@ static void buffer_push_packet(rtp_t *ctx) {
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
// each missing packet will be requested up to (latency_frames / 16) times
for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
if (!frame->ready && now - frame->last_resend > RESEND_TO) {
rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i);
// stop if one fails
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break;
frame->last_resend = now;
}
}
@@ -613,7 +615,10 @@ static void rtp_thread_func(void *arg) {
FD_ZERO(&fds);
for (i = 0; i < 3; i++) { FD_SET(ctx->rtp_sockets[i].sock, &fds); }
if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) {
if (ctx->stalled++ == 30*10) ctx->cmd_cb(RAOP_STALLED);
continue;
}
for (i = 0; i < 3; i++)
@@ -631,6 +636,7 @@ static void rtp_thread_func(void *arg) {
continue;
}
assert(plen <= MAX_PACKET);
ctx->stalled = 0;
type = packet[1] & ~0x80;
@@ -823,6 +829,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) {
ctx->rtp_host.sin_port = htons(ctx->rtp_sockets[CONTROL].rport);
if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) {
LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno));
return false;
}

View File

@@ -1,5 +1,5 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES json tools platform_config display wifi-manager
REQUIRES json tools platform_config display wifi-manager esp-tls platform_config
PRIV_REQUIRES soc esp32
)

View File

@@ -27,7 +27,9 @@
#include "soc/efuse_periph.h"
#include "driver/gpio.h"
#include "driver/spi_common_internal.h"
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/efuse.h"
#endif
#include "tools.h"
#include "monitor.h"
#include "messaging.h"
@@ -45,6 +47,7 @@ cJSON * gpio_list=NULL;
#define STR(macro) QUOTE(macro)
#endif
extern cJSON * get_gpio_list(bool refresh);
bool are_statistics_enabled(){
#if defined(CONFIG_FREERTOS_USE_TRACE_FACILITY) && defined (CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS)
return true;
@@ -590,7 +593,7 @@ const gpio_exp_config_t* config_gpio_exp_get(int index) {
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);
PARSE_PARAM_STR(item, "model", '=', config.model, sizeof(config.model)-1);
if ((p = strcasestr(item, "port")) != NULL) {
char port[8] = "";
@@ -644,6 +647,12 @@ const set_GPIO_struct_t * get_gpio_struct(){
#endif
#ifdef CONFIG_LED_RED_GPIO
gpio_struct.red.gpio = CONFIG_LED_RED_GPIO;
#endif
#if defined(CONFIG_POWER_GPIO) && CONFIG_POWER_GPIO != -1
gpio_struct.power.gpio = CONFIG_POWER_GPIO;
#endif
#ifdef CONFIG_POWER_GPIO_LEVEL
gpio_struct.power.level = CONFIG_POWER_GPIO_LEVEL;
#endif
if(nvs_item){
HANDLE_GPIO_STRUCT_MEMBER(amp,false);
@@ -656,6 +665,7 @@ const set_GPIO_struct_t * get_gpio_struct(){
HANDLE_GPIO_STRUCT_MEMBER(vcc,false);
HANDLE_GPIO_STRUCT_MEMBER(gnd,false);
HANDLE_GPIO_STRUCT_MEMBER(ir,false);
HANDLE_GPIO_STRUCT_MEMBER(power,false);
free(nvs_item);
}
@@ -821,6 +831,7 @@ cJSON * get_GPIO_nvs_list(cJSON * list) {
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,jack,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,green,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,red,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,power,"other");
ADD_GPIO_STRUCT_MEMBER_TO_ARRAY(ilist,gpios,spkfault,"other");
return ilist;
}
@@ -1088,6 +1099,9 @@ gpio_entry_t * get_gpio_by_name(char * name,char * group, bool refresh){
cJSON * get_psram_gpio_list(cJSON * list){
cJSON * llist=list;
#if CONFIG_IDF_TARGET_ESP32
const char * psram_dev = "psram";
const char * flash_dev = "flash";
const char * clk = "clk";
@@ -1096,7 +1110,6 @@ cJSON * get_psram_gpio_list(cJSON * list){
const char * spid_sd1_io = "spid_sd1_io";
const char * spiwp_sd3_io = "spiwp_sd3_io";
const char * spihd_sd2_io = "spihd_sd2_io";
cJSON * llist=list;
uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG);
uint32_t pkg_ver = chip_ver & 0x7;
@@ -1156,13 +1169,16 @@ cJSON * get_psram_gpio_list(cJSON * list){
cJSON_AddItemToArray(list,get_gpio_entry(clk,flash_dev,EFUSE_SPICONFIG_RET_SPICLK(spiconfig),true));
cJSON_AddItemToArray(list,get_gpio_entry(cs,flash_dev,EFUSE_SPICONFIG_RET_SPICS0(spiconfig),true));
}
#else
#pragma message("need to add esp32-s3 specific SPIRAM GPIO config code")
#endif
return llist;
}
/****************************************************************************************
*
*/
cJSON * get_gpio_list(bool refresh) {
cJSON * get_gpio_list_handler(bool refresh) {
gpio_num_t gpio_num;
if(gpio_list && !refresh){
return gpio_list;

View File

@@ -73,6 +73,7 @@ typedef struct {
gpio_with_level_t green;
gpio_with_level_t red;
gpio_with_level_t spkfault;
gpio_with_level_t power;
} set_GPIO_struct_t;
typedef struct {
@@ -117,7 +118,7 @@ bool is_spdif_config_locked();
esp_err_t free_gpio_entry( gpio_entry_t ** gpio);
gpio_entry_t * get_gpio_by_name(char * name,char * group, bool refresh);
gpio_entry_t * get_gpio_by_no(int gpionum, bool refresh);
cJSON * get_gpio_list(bool refresh);
bool is_dac_config_locked();
bool are_statistics_enabled();
const rotary_struct_t * config_rotary_get();

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,4 +35,4 @@ typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
bool create_infrared(int gpio, infrared_handler handler);
bool create_infrared(int gpio, infrared_handler handler, infrared_mode_t mode);

View File

@@ -13,10 +13,15 @@
#define I2C_SYSTEM_PORT 1
#define SPI_SYSTEM_HOST SPI2_HOST
#define RMT_NEXT_TX_CHANNEL() rmt_system_base_tx_channel++;
#define RMT_NEXT_RX_CHANNEL() rmt_system_base_rx_channel--;
extern int i2c_system_port;
extern int i2c_system_speed;
extern int spi_system_host;
extern int spi_system_dc_gpio;
extern int rmt_system_base_tx_channel;
extern int rmt_system_base_rx_channel;
typedef struct {
int timer, base_channel, max;
} pwm_system_t;

View File

@@ -537,7 +537,7 @@ static esp_err_t mpr121_init(gpio_exp_t* self) {
{ 0x57, 0x28 }, { 0x58, 0x14 }, /* ELE11: Touch Threshold, Release Threshold */
{ 0x5e, 0xcc }, /* ECR - must be set last */
{0, 0}
{ 0, 0 }
};
esp_err_t err = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -14,164 +14,496 @@
#include "esp_err.h"
#include "esp_log.h"
#include "driver/rmt.h"
#include "globdefs.h"
#include "infrared.h"
static const char* TAG = "IR";
#define RMT_RX_ACTIVE_LEVEL 0 /*!< If we connect with a IR receiver, the data is active low */
#define IR_TOOLS_FLAGS_PROTO_EXT (1 << 0) /*!< Enable Extended IR protocol */
#define IR_TOOLS_FLAGS_INVERSE (1 << 1) /*!< Inverse the IR signal, i.e. take high level as low, and vice versa */
#define RMT_RX_CHANNEL 0 /*!< RMT channel for receiver */
#define RMT_CLK_DIV 100 /*!< RMT counter clock divider */
#define RMT_TICK_10_US (80000000/RMT_CLK_DIV/100000) /*!< RMT counter value for 10 us.(Source clock is APB clock) */
static int8_t ir_gpio = -1;
#define NEC_HEADER_HIGH_US 9000 /*!< NEC protocol header: positive 9ms */
#define NEC_HEADER_LOW_US 4500 /*!< NEC protocol header: negative 4.5ms*/
#define NEC_BIT_ONE_HIGH_US 560 /*!< NEC protocol data bit 1: positive 0.56ms */
#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US) /*!< NEC protocol data bit 1: negative 1.69ms */
#define NEC_BIT_ZERO_HIGH_US 560 /*!< NEC protocol data bit 0: positive 0.56ms */
#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US) /*!< NEC protocol data bit 0: negative 0.56ms */
#define NEC_BIT_MARGIN 150 /*!< NEC parse margin time */
/**
* @brief IR device type
*
*/
typedef void *ir_dev_t;
#define NEC_ITEM_DURATION(d) ((d & 0x7fff)*10/RMT_TICK_10_US) /*!< Parse duration time from memory register value */
#define NEC_DATA_ITEM_NUM 34 /*!< NEC code item number: header + 32bit data + end */
#define rmt_item32_tIMEOUT_US 9500 /*!< RMT receiver timeout value(us) */
/**
* @brief IR parser type
*
*/
typedef struct ir_parser_s ir_parser_t;
/**
* @brief Type definition of IR parser
*
*/
struct ir_parser_s {
/**
* @brief Input raw data to IR parser
*
* @param[in] parser: Handle of IR parser
* @param[in] raw_data: Raw data which need decoding by IR parser
* @param[in] length: Length of raw data
*
* @return
* - ESP_OK: Input raw data successfully
* - ESP_ERR_INVALID_ARG: Input raw data failed because of invalid argument
* - ESP_FAIL: Input raw data failed because some other error occurred
*/
esp_err_t (*input)(ir_parser_t *parser, void *raw_data, uint32_t length);
/**
* @brief Get the scan code after decoding of raw data
*
* @param[in] parser: Handle of IR parser
* @param[out] address: Address of the scan code
* @param[out] command: Command of the scan code
* @param[out] repeat: Indicate if it's a repeat code
*
* @return
* - ESP_OK: Get scan code successfully
* - ESP_ERR_INVALID_ARG: Get scan code failed because of invalid arguments
* - ESP_FAIL: Get scan code failed because some error occurred
*/
esp_err_t (*get_scan_code)(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat);
};
typedef struct {
ir_dev_t dev_hdl; /*!< IR device handle */
uint32_t flags; /*!< Flags for IR parser, different flags will enable different features */
uint32_t margin_us; /*!< Timing parameter, indicating the tolerance to environment noise */
} ir_parser_config_t;
#define IR_PARSER_DEFAULT_CONFIG(dev) \
{ \
.dev_hdl = dev, \
.flags = 0, \
.margin_us = 200, \
}
ir_parser_t *ir_parser = NULL;
#define RMT_CHECK(a, str, goto_tag, ret_value, ...) \
do \
{ \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
ret = ret_value; \
goto goto_tag; \
} \
} while (0)
/****************************************************************************************
* NEC protocol
****************************************************************************************/
#define NEC_DATA_FRAME_RMT_WORDS (34)
#define NEC_REPEAT_FRAME_RMT_WORDS (2)
#define NEC_LEADING_CODE_HIGH_US (9000)
#define NEC_LEADING_CODE_LOW_US (4500)
#define NEC_PAYLOAD_ONE_HIGH_US (560)
#define NEC_PAYLOAD_ONE_LOW_US (1690)
#define NEC_PAYLOAD_ZERO_HIGH_US (560)
#define NEC_PAYLOAD_ZERO_LOW_US (560)
#define NEC_REPEAT_CODE_HIGH_US (9000)
#define NEC_REPEAT_CODE_LOW_US (2250)
#define NEC_ENDING_CODE_HIGH_US (560)
typedef struct {
ir_parser_t parent;
uint32_t flags;
uint32_t leading_code_high_ticks;
uint32_t leading_code_low_ticks;
uint32_t repeat_code_high_ticks;
uint32_t repeat_code_low_ticks;
uint32_t payload_logic0_high_ticks;
uint32_t payload_logic0_low_ticks;
uint32_t payload_logic1_high_ticks;
uint32_t payload_logic1_low_ticks;
uint32_t margin_ticks;
rmt_item32_t *buffer;
uint32_t cursor;
uint32_t last_address;
uint32_t last_command;
bool repeat;
bool inverse;
} nec_parser_t;
/****************************************************************************************
*
*/
static bool nec_check_in_range(int duration_ticks, int target_us, int margin_us) {
if(( NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
&& ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
return true;
static inline bool nec_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) {
return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks));
}
/****************************************************************************************
*
*/
static bool nec_parse_head(nec_parser_t *nec_parser) {
nec_parser->cursor = 0;
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
nec_check_in_range(item.duration0, nec_parser->leading_code_high_ticks, nec_parser->margin_ticks) &&
nec_check_in_range(item.duration1, nec_parser->leading_code_low_ticks, nec_parser->margin_ticks);
nec_parser->cursor += 1;
return ret;
}
/****************************************************************************************
*
*/
static bool nec_parse_logic0(nec_parser_t *nec_parser) {
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
nec_check_in_range(item.duration0, nec_parser->payload_logic0_high_ticks, nec_parser->margin_ticks) &&
nec_check_in_range(item.duration1, nec_parser->payload_logic0_low_ticks, nec_parser->margin_ticks);
return ret;
}
/****************************************************************************************
*
*/
static bool nec_parse_logic1(nec_parser_t *nec_parser) {
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
nec_check_in_range(item.duration0, nec_parser->payload_logic1_high_ticks, nec_parser->margin_ticks) &&
nec_check_in_range(item.duration1, nec_parser->payload_logic1_low_ticks, nec_parser->margin_ticks);
return ret;
}
/****************************************************************************************
*
*/
static esp_err_t nec_parse_logic(ir_parser_t *parser, bool *logic) {
esp_err_t ret = ESP_FAIL;
bool logic_value = false;
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
if (nec_parse_logic0(nec_parser)) {
logic_value = false;
ret = ESP_OK;
} else if (nec_parse_logic1(nec_parser)) {
logic_value = true;
ret = ESP_OK;
}
if (ret == ESP_OK) {
*logic = logic_value;
}
nec_parser->cursor += 1;
return ret;
}
/****************************************************************************************
*
*/
static bool nec_parse_repeat_frame(nec_parser_t *nec_parser) {
nec_parser->cursor = 0;
rmt_item32_t item = nec_parser->buffer[nec_parser->cursor];
bool ret = (item.level0 == nec_parser->inverse) && (item.level1 != nec_parser->inverse) &&
nec_check_in_range(item.duration0, nec_parser->repeat_code_high_ticks, nec_parser->margin_ticks) &&
nec_check_in_range(item.duration1, nec_parser->repeat_code_low_ticks, nec_parser->margin_ticks);
nec_parser->cursor += 1;
return ret;
}
/****************************************************************************************
*
*/
static esp_err_t nec_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) {
esp_err_t ret = ESP_OK;
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
RMT_CHECK(raw_data, "input data can't be null", err, ESP_ERR_INVALID_ARG);
nec_parser->buffer = raw_data;
// Data Frame costs 34 items and Repeat Frame costs 2 items
if (length == NEC_DATA_FRAME_RMT_WORDS) {
nec_parser->repeat = false;
} else if (length == NEC_REPEAT_FRAME_RMT_WORDS) {
nec_parser->repeat = true;
} else {
return false;
ret = ESP_FAIL;
}
return ret;
err:
return ret;
}
/****************************************************************************************
*
*/
static bool nec_header_if(rmt_item32_t* item) {
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
&& nec_check_in_range(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
&& nec_check_in_range(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
/****************************************************************************************
*
*/
static bool nec_bit_one_if(rmt_item32_t* item) {
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
&& nec_check_in_range(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
&& nec_check_in_range(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
/****************************************************************************************
*
*/
static bool nec_bit_zero_if(rmt_item32_t* item) {
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
&& nec_check_in_range(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
&& nec_check_in_range(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
/****************************************************************************************
*
*/
static int nec_parse_items(rmt_item32_t* item, int item_num, uint16_t* addr, uint16_t* data) {
int w_len = item_num;
if(w_len < NEC_DATA_ITEM_NUM) {
return -1;
}
int i = 0, j = 0;
if(!nec_header_if(item++)) {
return -1;
}
uint16_t addr_t = 0;
for(j = 15; j >= 0; j--) {
if(nec_bit_one_if(item)) {
addr_t |= (1 << j);
} else if(nec_bit_zero_if(item)) {
addr_t |= (0 << j);
} else {
return -1;
static esp_err_t nec_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) {
esp_err_t ret = ESP_FAIL;
uint32_t addr = 0;
uint32_t cmd = 0;
bool logic_value = false;
nec_parser_t *nec_parser = __containerof(parser, nec_parser_t, parent);
if (nec_parser->repeat) {
if (nec_parse_repeat_frame(nec_parser)) {
*address = nec_parser->last_address;
*command = nec_parser->last_command;
*repeat = true;
ret = ESP_OK;
}
item++;
i++;
}
uint16_t data_t = 0;
for(j = 15; j >= 0; j--) {
if(nec_bit_one_if(item)) {
data_t |= (1 << j);
} else if(nec_bit_zero_if(item)) {
data_t |= (0 << j);
} else {
return -1;
} else {
if (nec_parse_head(nec_parser)) {
// for the forgetful, need to do a bitreverse
for (int i = 15; i >= 0; i--) {
if (nec_parse_logic(parser, &logic_value) == ESP_OK) {
addr |= (logic_value << i);
}
}
for (int i = 15; i >= 0; i--) {
if (nec_parse_logic(parser, &logic_value) == ESP_OK) {
cmd |= (logic_value << i);
}
}
*address = addr;
*command = cmd;
*repeat = false;
// keep it as potential repeat code
nec_parser->last_address = addr;
nec_parser->last_command = cmd;
ret = ESP_OK;
}
item++;
i++;
}
*addr = addr_t;
*data = data_t;
return i;
return ret;
}
/****************************************************************************************
*
*/
void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
ir_parser_t *ir_parser_rmt_new_nec(const ir_parser_config_t *config) {
ir_parser_t *ret = NULL;
nec_parser_t *nec_parser = calloc(1, sizeof(nec_parser_t));
nec_parser->flags = config->flags;
if (config->flags & IR_TOOLS_FLAGS_INVERSE) {
nec_parser->inverse = true;
}
uint32_t counter_clk_hz = 0;
RMT_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK,
"get rmt counter clock failed", err, NULL);
float ratio = (float)counter_clk_hz / 1e6;
nec_parser->leading_code_high_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_HIGH_US);
nec_parser->leading_code_low_ticks = (uint32_t)(ratio * NEC_LEADING_CODE_LOW_US);
nec_parser->repeat_code_high_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_HIGH_US);
nec_parser->repeat_code_low_ticks = (uint32_t)(ratio * NEC_REPEAT_CODE_LOW_US);
nec_parser->payload_logic0_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_HIGH_US);
nec_parser->payload_logic0_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ZERO_LOW_US);
nec_parser->payload_logic1_high_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_HIGH_US);
nec_parser->payload_logic1_low_ticks = (uint32_t)(ratio * NEC_PAYLOAD_ONE_LOW_US);
nec_parser->margin_ticks = (uint32_t)(ratio * config->margin_us);
nec_parser->parent.input = nec_parser_input;
nec_parser->parent.get_scan_code = nec_parser_get_scan_code;
return &nec_parser->parent;
err:
return ret;
}
/****************************************************************************************
* RC5 protocol
****************************************************************************************/
#define RC5_MAX_FRAME_RMT_WORDS (14) // S1+S2+T+ADDR(5)+CMD(6)
#define RC5_PULSE_DURATION_US (889)
typedef struct {
ir_parser_t parent;
uint32_t flags;
uint32_t pulse_duration_ticks;
uint32_t margin_ticks;
rmt_item32_t *buffer;
uint32_t buffer_len;
uint32_t last_command;
uint32_t last_address;
bool last_t_bit;
} rc5_parser_t;
/****************************************************************************************
*
*/
static inline bool rc5_check_in_range(uint32_t raw_ticks, uint32_t target_ticks, uint32_t margin_ticks) {
return (raw_ticks < (target_ticks + margin_ticks)) && (raw_ticks > (target_ticks - margin_ticks));
}
/****************************************************************************************
*
*/
static esp_err_t rc5_parser_input(ir_parser_t *parser, void *raw_data, uint32_t length) {
esp_err_t ret = ESP_OK;
rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent);
rc5_parser->buffer = raw_data;
rc5_parser->buffer_len = length;
if (length > RC5_MAX_FRAME_RMT_WORDS) {
ret = ESP_FAIL;
}
return ret;
}
/****************************************************************************************
*
*/
static inline bool rc5_duration_one_unit(rc5_parser_t *rc5_parser, uint32_t duration) {
return (duration < (rc5_parser->pulse_duration_ticks + rc5_parser->margin_ticks)) &&
(duration > (rc5_parser->pulse_duration_ticks - rc5_parser->margin_ticks));
}
/****************************************************************************************
*
*/
static inline bool rc5_duration_two_unit(rc5_parser_t *rc5_parser, uint32_t duration) {
return (duration < (rc5_parser->pulse_duration_ticks * 2 + rc5_parser->margin_ticks)) &&
(duration > (rc5_parser->pulse_duration_ticks * 2 - rc5_parser->margin_ticks));
}
/****************************************************************************************
*
*/
static esp_err_t rc5_parser_get_scan_code(ir_parser_t *parser, uint32_t *address, uint32_t *command, bool *repeat) {
esp_err_t ret = ESP_FAIL;
uint32_t parse_result = 0; // 32 bit is enough to hold the parse result of one RC5 frame
uint32_t addr = 0;
uint32_t cmd = 0;
bool s1 = true;
bool s2 = true;
bool t = false;
bool exchange = false;
rc5_parser_t *rc5_parser = __containerof(parser, rc5_parser_t, parent);
for (int i = 0; i < rc5_parser->buffer_len; i++) {
if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration0)) {
parse_result <<= 1;
parse_result |= exchange;
if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration1)) {
exchange = !exchange;
}
} else if (rc5_duration_two_unit(rc5_parser, rc5_parser->buffer[i].duration0)) {
parse_result <<= 1;
parse_result |= rc5_parser->buffer[i].level0;
parse_result <<= 1;
parse_result |= !rc5_parser->buffer[i].level0;
if (rc5_duration_one_unit(rc5_parser, rc5_parser->buffer[i].duration1)) {
exchange = !exchange;
}
} else {
goto out;
}
}
if (!(rc5_parser->flags & IR_TOOLS_FLAGS_INVERSE)) {
parse_result = ~parse_result;
}
s1 = ((parse_result & 0x2000) >> 13) & 0x01;
s2 = ((parse_result & 0x1000) >> 12) & 0x01;
t = ((parse_result & 0x800) >> 11) & 0x01;
// Check S1, must be 1
if (s1) {
if (!(rc5_parser->flags & IR_TOOLS_FLAGS_PROTO_EXT) && !s2) {
// Not standard RC5 protocol, but S2 is 0
goto out;
}
addr = (parse_result & 0x7C0) >> 6;
cmd = (parse_result & 0x3F);
if (!s2) {
cmd |= 1 << 6;
}
*repeat = (t == rc5_parser->last_t_bit && addr == rc5_parser->last_address && cmd == rc5_parser->last_command);
*address = addr;
*command = cmd;
rc5_parser->last_address = addr;
rc5_parser->last_command = cmd;
rc5_parser->last_t_bit = t;
ret = ESP_OK;
}
out:
return ret;
}
/****************************************************************************************
*
*/
ir_parser_t *ir_parser_rmt_new_rc5(const ir_parser_config_t *config) {
ir_parser_t *ret = NULL;
rc5_parser_t *rc5_parser = calloc(1, sizeof(rc5_parser_t));
rc5_parser->flags = config->flags;
uint32_t counter_clk_hz = 0;
RMT_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev_hdl, &counter_clk_hz) == ESP_OK,
"get rmt counter clock failed", err, NULL);
float ratio = (float)counter_clk_hz / 1e6;
rc5_parser->pulse_duration_ticks = (uint32_t)(ratio * RC5_PULSE_DURATION_US);
rc5_parser->margin_ticks = (uint32_t)(ratio * config->margin_us);
rc5_parser->parent.input = rc5_parser_input;
rc5_parser->parent.get_scan_code = rc5_parser_get_scan_code;
return &rc5_parser->parent;
err:
return ret;
}
/****************************************************************************************
*
*/
bool infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
size_t rx_size = 0;
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 10 / portTICK_RATE_MS);
bool decoded = false;
if (item) {
uint16_t addr, cmd;
int offset = 0;
while (1) {
// parse data value from ringbuffer.
int res = nec_parse_items(item + offset, rx_size / 4 - offset, &addr, &cmd);
if (res > 0) {
offset += res + 1;
handler(addr, cmd);
ESP_LOGD(TAG, "RMT RCV --- addr: 0x%04x cmd: 0x%04x", addr, cmd);
} else break;
uint32_t addr, cmd;
bool repeat = false;
rx_size /= 4; // one RMT = 4 Bytes
if (ir_parser->input(ir_parser, item, rx_size) == ESP_OK) {
if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) {
decoded = true;
handler(addr, cmd);
ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd);
}
}
// if we have not decoded data but lenght is reasonnable, dump it
if (!decoded && rx_size > RC5_MAX_FRAME_RMT_WORDS) {
ESP_LOGI(TAG, "can't decode IR signal of len %d", rx_size);
ESP_LOG_BUFFER_HEX(TAG, item, rx_size * 4);
}
// after parsing the data, return spaces to ringbuffer.
vRingbufferReturnItem(rb, (void*) item);
}
return decoded;
}
/****************************************************************************************
*
*/
void infrared_init(RingbufHandle_t *rb, int gpio) {
rmt_config_t rmt_rx;
ESP_LOGI(TAG, "Starting Infrared Receiver on gpio %d", gpio);
// initialize RMT driver
rmt_rx.channel = RMT_RX_CHANNEL;
rmt_rx.gpio_num = gpio;
rmt_rx.clk_div = RMT_CLK_DIV;
rmt_rx.mem_block_num = 1;
rmt_rx.rmt_mode = RMT_MODE_RX;
rmt_rx.rx_config.filter_en = true;
rmt_rx.rx_config.filter_ticks_thresh = 100;
rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
rmt_config(&rmt_rx);
rmt_driver_install(rmt_rx.channel, 1000, 0);
// get RMT RX ringbuffer
rmt_get_ringbuf_handle(RMT_RX_CHANNEL, rb);
rmt_rx_start(RMT_RX_CHANNEL, 1);
int8_t infrared_gpio(void) {
return ir_gpio;
};
/****************************************************************************************
*
*/
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode) {
int rmt_channel = RMT_NEXT_RX_CHANNEL();
rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(gpio, rmt_channel);
rmt_config(&rmt_rx_config);
rmt_driver_install(rmt_rx_config.channel, 1000, 0);
ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t) rmt_rx_config.channel);
ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version)
ir_parser = (mode == IR_NEC) ? ir_parser_rmt_new_nec(&ir_parser_config) : ir_parser_rmt_new_rc5(&ir_parser_config);
ir_gpio = gpio;
// get RMT RX ringbuffer
rmt_get_ringbuf_handle(rmt_channel, rb);
rmt_rx_start(rmt_channel, 1);
ESP_LOGI(TAG, "Starting Infrared Receiver mode %s on gpio %d and channel %d", mode == IR_NEC ? "nec" : "rc5", gpio, rmt_channel);
}

View File

@@ -11,7 +11,11 @@
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
typedef enum {IR_NEC, IR_RC5} infrared_mode_t;
typedef void (*infrared_handler)(uint16_t addr, uint16_t cmd);
void infrared_receive(RingbufHandle_t rb, infrared_handler handler);
void infrared_init(RingbufHandle_t *rb, int gpio);
bool infrared_receive(RingbufHandle_t rb, infrared_handler handler);
void infrared_init(RingbufHandle_t *rb, int gpio, infrared_mode_t mode);
int8_t infrared_gpio(void);

View File

@@ -18,97 +18,145 @@
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "driver/rmt.h"
#include "platform_config.h"
#include "gpio_exp.h"
#include "led.h"
#include "globdefs.h"
#include "accessors.h"
#include "services.h"
#define MAX_LED 8
#define BLOCKTIME 10 // up to portMAX_DELAY
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define LEDC_SPEED_MODE LEDC_LOW_SPEED_MODE
#else
#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
#endif
static const char *TAG = "led";
#define RMT_CLK (40/2)
static int8_t led_rmt_channel = -1;
static uint32_t scale24(uint32_t bright, uint8_t);
static const struct rmt_led_param_s {
led_type_t type;
uint8_t bits;
// number of ticks in nanoseconds converted in RMT_CLK ticks
rmt_item32_t bit_0;
rmt_item32_t bit_1;
uint32_t green, red;
uint32_t (*scale)(uint32_t, uint8_t);
} rmt_led_param[] = {
{ LED_WS2812, 24, {{{350 / RMT_CLK, 1, 1000 / RMT_CLK, 0}}}, {{{1000 / RMT_CLK, 1, 350 / RMT_CLK, 0}}}, 0xff0000, 0x00ff00, scale24 },
{ .type = -1 } };
static EXT_RAM_ATTR struct led_s {
gpio_num_t gpio;
bool on;
int onstate;
uint32_t color;
int ontime, offtime;
int pwm;
int bright;
int channel;
const struct rmt_led_param_s *rmt;
int pushedon, pushedoff;
bool pushed;
TimerHandle_t timer;
} leds[MAX_LED];
// can't use EXT_RAM_ATTR for initialized structure
static struct {
static struct led_config_s {
int gpio;
int active;
int pwm;
} green = { .gpio = CONFIG_LED_GREEN_GPIO, .active = 0, .pwm = -1 },
red = { .gpio = CONFIG_LED_RED_GPIO, .active = 0, .pwm = -1 };
int color;
int bright;
led_type_t type;
} green = { .gpio = CONFIG_LED_GREEN_GPIO, .color = 0, .bright = -1, .type = LED_GPIO },
red = { .gpio = CONFIG_LED_RED_GPIO, .color = 0, .bright = -1, .type = LED_GPIO };
static int led_max = 2;
/****************************************************************************************
*
*
*/
static void set_level(struct led_s *led, bool on) {
if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_x(led->gpio, on ? led->onstate : !led->onstate);
else {
ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
}
static uint32_t scale24(uint32_t color, uint8_t scale) {
uint32_t scaled = (((color & 0xff0000) >> 16) * scale / 100) << 16;
scaled |= (((color & 0xff00) >> 8) * scale / 100) << 8;
scaled |= (color & 0xff) * scale / 100;
return scaled;
}
/****************************************************************************************
*
*
*/
static void set_level(struct led_s *led, bool on) {
if (led->rmt) {
uint32_t data = on ? led->rmt->scale(led->color, led->bright) : 0;
uint32_t mask = 1 << (led->rmt->bits - 1);
rmt_item32_t buffer[led->rmt->bits];
for (uint32_t bit = 0; bit < led->rmt->bits; bit++) {
uint32_t set = data & mask;
buffer[bit] = set ? led->rmt->bit_1 : led->rmt->bit_0;
mask >>= 1;
}
rmt_write_items(led->channel, buffer, led->rmt->bits, false);
} else if (led->bright < 0 || led->gpio >= GPIO_NUM_MAX) {
gpio_set_level_x(led->gpio, on ? led->color : !led->color);
} else {
ledc_set_duty(LEDC_SPEED_MODE, led->channel, on ? led->bright : (led->color ? 0 : pwm_system.max));
ledc_update_duty(LEDC_SPEED_MODE, led->channel);
}
}
/****************************************************************************************
*
*/
static void vCallbackFunction( TimerHandle_t xTimer ) {
struct led_s *led = (struct led_s*) pvTimerGetTimerID (xTimer);
if (!led->timer) return;
led->on = !led->on;
ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (pwm:%d)", led->gpio, led->on, led->pwm);
ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (bright:%d)", led->gpio, led->on, led->bright);
set_level(led, led->on);
// was just on for a while
if (!led->on && led->offtime == -1) return;
// regular blinking
xTimerChangePeriod(xTimer, (led->on ? led->ontime : led->offtime) / portTICK_RATE_MS, BLOCKTIME);
}
/****************************************************************************************
*
*
*/
bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false;
ESP_LOGD(TAG,"led_blink_core %d on:%d off:%d, pushed:%u", idx, ontime, offtime, pushed);
if (leds[idx].timer) {
// normal requests waits if a pop is pending
if (!pushed && leds[idx].pushed) {
leds[idx].pushedon = ontime;
leds[idx].pushedoff = offtime;
leds[idx].pushedon = ontime;
leds[idx].pushedoff = offtime;
return true;
}
xTimerStop(leds[idx].timer, BLOCKTIME);
}
// save current state if not already pushed
if (!leds[idx].pushed) {
leds[idx].pushedon = leds[idx].ontime;
leds[idx].pushedoff = leds[idx].offtime;
leds[idx].pushedoff = leds[idx].offtime;
leds[idx].pushed = pushed;
}
}
// then set new one
leds[idx].ontime = ontime;
leds[idx].offtime = offtime;
leds[idx].offtime = offtime;
if (ontime == 0) {
ESP_LOGD(TAG,"led %d, setting reverse level", idx);
set_level(leds + idx, false);
@@ -126,39 +174,44 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio);
if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false;
}
return true;
}
/****************************************************************************************
*
*/
bool led_brightness(int idx, int pwm) {
if (pwm > 100) pwm = 100;
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
if (!leds[idx].onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
ledc_set_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel, leds[idx].pwm);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel);
return true;
}
/****************************************************************************************
*
*
*/
bool led_brightness(int idx, int bright) {
if (bright > 100) bright = 100;
if (leds[idx].rmt) {
leds[idx].bright = bright;
} else {
leds[idx].bright = pwm_system.max * powf(bright / 100.0, 3);
if (!leds[idx].color) leds[idx].bright = pwm_system.max - leds[idx].bright;
ledc_set_duty(LEDC_SPEED_MODE, leds[idx].channel, leds[idx].bright);
ledc_update_duty(LEDC_SPEED_MODE, leds[idx].channel);
}
return true;
}
/****************************************************************************************
*
*/
bool led_unpush(int idx) {
if (!leds[idx].gpio || leds[idx].gpio<0) return false;
led_blink_core(idx, leds[idx].pushedon, leds[idx].pushedoff, true);
leds[idx].pushed = false;
return true;
}
}
/****************************************************************************************
*
*
*/
int led_allocate(void) {
if (led_max < MAX_LED) return led_max++;
@@ -166,83 +219,127 @@ int led_allocate(void) {
}
/****************************************************************************************
*
*
*/
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type) {
if (gpio < 0) {
ESP_LOGW(TAG,"LED GPIO -1 ignored");
return false;
}
ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s", idx, gpio, onstate>0?"On":"Off");
if (idx >= MAX_LED) return false;
leds[idx].gpio = gpio;
leds[idx].onstate = onstate;
leds[idx].pwm = -1;
if (pwm < 0 || gpio >= GPIO_NUM_MAX) {
if (idx >= MAX_LED) return false;
if (bright > 100) bright = 100;
leds[idx].gpio = gpio;
leds[idx].color = color;
leds[idx].rmt = NULL;
leds[idx].bright = -1;
if (type != LED_GPIO) {
// first make sure we have a known addressable led
for (const struct rmt_led_param_s *p = rmt_led_param; !leds[idx].rmt && p->type >= 0; p++) if (p->type == type) leds[idx].rmt = p;
if (!leds[idx].rmt) return false;
if (led_rmt_channel < 0) led_rmt_channel = RMT_NEXT_TX_CHANNEL();
leds[idx].channel = led_rmt_channel;
leds[idx].bright = bright > 0 ? bright : 100;
// set counter clock to 40MHz
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, leds[idx].channel);
config.clk_div = 2;
rmt_config(&config);
rmt_driver_install(config.channel, 0, 0);
} else if (bright < 0 || gpio >= GPIO_NUM_MAX) {
gpio_pad_select_gpio_x(gpio);
gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT);
} else {
} else {
leds[idx].channel = pwm_system.base_channel++;
leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
if (!onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
leds[idx].bright = pwm_system.max * powf(bright / 100.0, 3);
if (!color) leds[idx].bright = pwm_system.max - leds[idx].bright;
ledc_channel_config_t ledc_channel = {
.channel = leds[idx].channel,
.duty = leds[idx].pwm,
.duty = leds[idx].bright,
.gpio_num = gpio,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.speed_mode = LEDC_SPEED_MODE,
.hpoint = 0,
.timer_sel = pwm_system.timer,
};
ledc_channel_config(&ledc_channel);
}
set_level(leds + idx, false);
ESP_LOGD(TAG,"PWM Index %d, GPIO %d, on state %s, pwm %d%%", idx, gpio, onstate > 0 ? "On" : "Off", pwm);
ESP_LOGD(TAG,"Index %d, GPIO %d, color/onstate %d / RMT %d, bright %d%%", idx, gpio, color, type, bright);
return true;
}
/****************************************************************************************
*
*
*/
static void led_suspend(void) {
led_off(LED_GREEN);
led_off(LED_RED);
}
/****************************************************************************************
*
*/
void set_led_gpio(int gpio, char *value) {
char *p;
if (strcasestr(value, "green")) {
green.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) green.active = atoi(p + 1);
} else if (strcasestr(value, "red")) {
red.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) red.active = atoi(p + 1);
}
struct led_config_s *config;
if (strcasestr(value, "green")) config = &green;
else if (strcasestr(value, "red"))config = &red;
else return;
config->gpio = gpio;
char *p = value;
while ((p = strchr(p, ':')) != NULL) {
p++;
if ((strcasestr(p, "ws2812")) != NULL) config->type = LED_WS2812;
else config->color = atoi(p);
}
if (config->type != LED_GPIO) {
for (const struct rmt_led_param_s *p = rmt_led_param; p->type >= 0; p++) {
if (p->type == config->type) {
if (config == &green) config->color = p->green;
else config->color = p->red;
break;
}
}
}
}
void led_svc_init(void) {
#ifdef CONFIG_LED_GREEN_GPIO_LEVEL
green.active = CONFIG_LED_GREEN_GPIO_LEVEL;
green.color = CONFIG_LED_GREEN_GPIO_LEVEL;
#endif
#ifdef CONFIG_LED_RED_GPIO_LEVEL
red.active = CONFIG_LED_RED_GPIO_LEVEL;
red.color = CONFIG_LED_RED_GPIO_LEVEL;
#endif
#ifndef CONFIG_LED_LOCKED
parse_set_GPIO(set_led_gpio);
#endif
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness");
if (nvs_item) {
PARSE_PARAM(nvs_item, "green", '=', green.pwm);
PARSE_PARAM(nvs_item, "red", '=', red.pwm);
PARSE_PARAM(nvs_item, "green", '=', green.bright);
PARSE_PARAM(nvs_item, "red", '=', red.bright);
free(nvs_item);
}
led_config(LED_GREEN, green.gpio, green.active, green.pwm);
led_config(LED_RED, red.gpio, red.active, red.pwm);
ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, red.gpio, red.active, red.pwm );
led_config(LED_GREEN, green.gpio, green.color, green.bright, green.type);
led_config(LED_RED, red.gpio, red.color, red.bright, red.type);
// make sure we switch off all leds (useful for gpio expanders)
services_sleep_setsuspend(led_suspend);
ESP_LOGI(TAG,"Configuring LEDs green:%d (on:%d rmt:%d %d%% ), red:%d (on:%d rmt:%d %d%% )",
green.gpio, green.color, green.type, green.bright,
red.gpio, red.color, red.type, red.bright);
}

View File

@@ -1,4 +1,4 @@
/*
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
@@ -8,20 +8,22 @@
* https://opensource.org/licenses/MIT
*
*/
#ifndef LED_H
#define LED_H
#include "driver/gpio.h"
enum { LED_GREEN = 0, LED_RED };
typedef enum { LED_GPIO = -1, LED_WS2812 } led_type_t;
#define led_on(idx) led_blink_core(idx, 1, 0, false)
#define led_off(idx) led_blink_core(idx, 0, 0, false)
#define led_blink(idx, on, off) led_blink_core(idx, on, off, false)
#define led_blink_pushed(idx, on, off) led_blink_core(idx, on, off, true)
bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm);
bool led_brightness(int idx, int percent);
// if type is LED_GPIO then color set the GPIO logic value for "on"
bool led_config(int idx, gpio_num_t gpio, int color, int bright, led_type_t type);
bool led_brightness(int idx, int percent);
bool led_blink_core(int idx, int ontime, int offtime, bool push);
bool led_unpush(int idx);
int led_allocate(void);

View File

@@ -14,6 +14,7 @@
#include "freertos/timers.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_task.h"
#include "monitor.h"
#include "driver/gpio.h"
#include "buttons.h"
@@ -25,15 +26,14 @@
#include "cJSON.h"
#include "tools.h"
#define PSEUDO_IDLE_STACK_SIZE (6*1024)
#define MONITOR_TIMER (10*1000)
#define SCRATCH_SIZE 256
static const char *TAG = "monitor";
static TimerHandle_t monitor_timer;
static monitor_gpio_t jack = { CONFIG_JACK_GPIO, 0 };
static monitor_gpio_t spkfault = { CONFIG_SPKFAULT_GPIO, 0 };
void (*pseudo_idle_svc)(uint32_t now);
void (*jack_handler_svc)(bool inserted);
bool jack_inserted_svc(void);
@@ -41,36 +41,39 @@ bool jack_inserted_svc(void);
void (*spkfault_handler_svc)(bool inserted);
bool spkfault_svc(void);
static monitor_gpio_t jack = { CONFIG_JACK_GPIO, 0 };
static monitor_gpio_t spkfault = { CONFIG_SPKFAULT_GPIO, 0 };
static bool monitor_stats;
/****************************************************************************************
*
*
*/
static void task_stats( cJSON* top ) {
#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
#pragma message("Compiled with trace facility")
static struct {
TaskStatus_t *tasks;
uint32_t total, n;
} current, previous;
cJSON * tlist=cJSON_CreateArray();
current.n = uxTaskGetNumberOfTasks();
current.tasks = malloc_init_external( current.n * sizeof( TaskStatus_t ) );
current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
cJSON_AddNumberToObject(top,"ntasks",current.n);
static EXT_RAM_ATTR char scratch[SCRATCH_SIZE];
*scratch = '\0';
char scratch[SCRATCH_SIZE] = { };
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
#pragma message("Compiled with runtime stats")
uint32_t elapsed = current.total - previous.total;
for(int i = 0, n = 0; i < current.n; i++ ) {
for (int j = 0; j < previous.n; j++) {
if (current.tasks[i].xTaskNumber == previous.tasks[j].xTaskNumber) {
n += snprintf(scratch + n, SCRATCH_SIZE - n, "%16s (%u) %2u%% s:%5u", current.tasks[i].pcTaskName,
n += snprintf(scratch + n, SCRATCH_SIZE - n, "%16s (%u) %2u%% s:%5u", current.tasks[i].pcTaskName,
current.tasks[i].eCurrentState,
100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed,
100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed,
current.tasks[i].usStackHighWaterMark);
cJSON * t=cJSON_CreateObject();
cJSON_AddNumberToObject(t,"cpu",100 * (current.tasks[i].ulRunTimeCounter - previous.tasks[j].ulRunTimeCounter) / elapsed);
@@ -84,11 +87,11 @@ static void task_stats( cJSON* top ) {
if (i % 3 == 2 || i == current.n - 1) {
ESP_LOGI(TAG, "%s", scratch);
n = 0;
}
}
break;
}
}
}
}
#else
#pragma message("Compiled WITHOUT runtime stats")
@@ -105,21 +108,26 @@ static void task_stats( cJSON* top ) {
if (i % 3 == 2 || i == current.n - 1) {
ESP_LOGI(TAG, "%s", scratch);
n = 0;
}
}
}
#endif
#endif
cJSON_AddItemToObject(top,"tasks",tlist);
if (previous.tasks) free(previous.tasks);
previous = current;
#else
#pragma message("Compiled WITHOUT trace facility")
#endif
#else
#pragma message("Compiled WITHOUT trace facility")
#endif
}
/****************************************************************************************
*
*
*/
static void monitor_callback(TimerHandle_t xTimer) {
static void monitor_trace(uint32_t now) {
static uint32_t last;
if (now < last + MONITOR_TIMER) return;
last = now;
cJSON * top=cJSON_CreateObject();
cJSON_AddNumberToObject(top,"free_iram",heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
cJSON_AddNumberToObject(top,"min_free_iram",heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL));
@@ -133,7 +141,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_free_size(MALLOC_CAP_DMA),
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
task_stats(top);
char * top_a= cJSON_PrintUnformatted(top);
if(top_a){
@@ -144,7 +152,7 @@ static void monitor_callback(TimerHandle_t xTimer) {
}
/****************************************************************************************
*
*
*/
static void jack_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Jack %s", event == BUTTON_PRESSED ? "inserted" : "removed");
@@ -152,7 +160,7 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
}
/****************************************************************************************
*
*
*/
bool jack_inserted_svc (void) {
if (jack.gpio != -1) return button_is_pressed(jack.gpio, NULL);
@@ -160,7 +168,7 @@ bool jack_inserted_svc (void) {
}
/****************************************************************************************
*
*
*/
static void spkfault_handler_default(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGD(TAG, "Speaker status %s", event == BUTTON_PRESSED ? "faulty" : "normal");
@@ -170,22 +178,22 @@ static void spkfault_handler_default(void *id, button_event_e event, button_pres
}
/****************************************************************************************
*
*
*/
bool spkfault_svc (void) {
return button_is_pressed(spkfault.gpio, NULL);
}
/****************************************************************************************
*
*
*/
#ifndef CONFIG_JACK_LOCKED
static void set_jack_gpio(int gpio, char *value) {
if (strcasestr(value, "jack")) {
char *p;
jack.gpio = gpio;
jack.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) jack.active = atoi(p + 1);
}
}
else {
jack.gpio = -1;
}
@@ -193,15 +201,15 @@ static void set_jack_gpio(int gpio, char *value) {
#endif
/****************************************************************************************
*
*
*/
#ifndef CONFIG_SPKFAULT_LOCKED
static void set_spkfault_gpio(int gpio, char *value) {
if (strcasestr(value, "spkfault")) {
char *p;
spkfault.gpio = gpio;
spkfault.gpio = gpio;
if ((p = strchr(value, ':')) != NULL) spkfault.active = atoi(p + 1);
}
}
else {
spkfault.gpio = -1;
}
@@ -209,28 +217,41 @@ static void set_spkfault_gpio(int gpio, char *value) {
#endif
/****************************************************************************************
*
*
*/
static void pseudo_idle(void *arg) {
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
uint32_t now = pdTICKS_TO_MS(xTaskGetTickCount());
if (monitor_stats) monitor_trace(now);
if (pseudo_idle_svc) pseudo_idle_svc(now);
}
}
/****************************************************************************************
*
*/
void monitor_svc_init(void) {
ESP_LOGI(TAG, "Initializing monitoring");
#ifdef CONFIG_JACK_GPIO_LEVEL
jack.active = CONFIG_JACK_GPIO_LEVEL;
#endif
#ifndef CONFIG_JACK_LOCKED
parse_set_GPIO(set_jack_gpio);
#endif
#endif
// re-use button management for jack handler, it's a GPIO after all
if (jack.gpio != -1) {
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", jack.active ? "high" : "low", jack.gpio);
button_create(NULL, jack.gpio, jack.active ? BUTTON_HIGH : BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
}
}
#ifdef CONFIG_SPKFAULT_GPIO_LEVEL
spkfault.active = CONFIG_SPKFAULT_GPIO_LEVEL;
#endif
#endif
#ifndef CONFIG_SPKFAULT_LOCKED
parse_set_GPIO(set_spkfault_gpio);
@@ -238,18 +259,15 @@ void monitor_svc_init(void) {
// re-use button management for speaker fault handler, it's a GPIO after all
if (spkfault.gpio != -1) {
ESP_LOGI(TAG,"Adding speaker fault (%s) detection GPIO %d", spkfault.active ? "high" : "low", spkfault.gpio);
ESP_LOGI(TAG,"Adding speaker fault (%s) detection GPIO %d", spkfault.active ? "high" : "low", spkfault.gpio);
button_create(NULL, spkfault.gpio, spkfault.active ? BUTTON_HIGH : BUTTON_LOW, false, 0, spkfault_handler_default, 0, -1);
}
}
// do we want stats
char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
if (p && (*p == '1' || *p == 'Y' || *p == 'y')) {
monitor_timer = xTimerCreate("monitor", MONITOR_TIMER / portTICK_RATE_MS, pdTRUE, NULL, monitor_callback);
xTimerStart(monitor_timer, portMAX_DELAY);
}
monitor_stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
FREE_AND_NULL(p);
ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu) dma:%zu (min:%zu)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
@@ -257,18 +275,24 @@ void monitor_svc_init(void) {
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_free_size(MALLOC_CAP_DMA),
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
// pseudo-idle callback => don't use FreeRTOS idle callbacks so we can block (should not but ...)
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
static EXT_RAM_ATTR StackType_t xStack[PSEUDO_IDLE_STACK_SIZE] __attribute__ ((aligned (4)));
xTaskCreateStatic( (TaskFunction_t) pseudo_idle, "pseudo_idle", PSEUDO_IDLE_STACK_SIZE,
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer );
}
/****************************************************************************************
*
*
*/
monitor_gpio_t * get_spkfault_gpio(){
return &spkfault ;
}
return &spkfault ;
}
/****************************************************************************************
*
*
*/
monitor_gpio_t * get_jack_insertion_gpio(){
return &jack;
}
}

View File

@@ -14,13 +14,15 @@ typedef struct {
int active;
} monitor_gpio_t;
extern void (*pseudo_idle_svc)(uint32_t now);
extern void (*jack_handler_svc)(bool inserted);
extern bool jack_inserted_svc(void);
extern void (*spkfault_handler_svc)(bool inserted);
extern bool spkfault_svc(void);
extern void (*battery_handler_svc)(float value);
extern void (*battery_handler_svc)(float value, int cells);
extern float battery_value_svc(void);
extern uint16_t battery_level_svc(void);

View File

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

View File

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

View File

@@ -30,10 +30,16 @@
#include "cspot_private.h"
#include "cspot_sink.h"
#include "platform_config.h"
#include "nvs_utilities.h"
#include "tools.h"
static class cspotPlayer *player;
static const struct {
const char *ns;
const char *credentials;
} spotify_ns = { .ns = "spotify", .credentials = "credentials" };
/****************************************************************************************
* Player's main class & task
*/
@@ -42,7 +48,12 @@ class cspotPlayer : public bell::Task {
private:
std::string name;
bell::WrappedSemaphore clientConnected;
std::atomic<bool> isPaused, isConnected;
std::atomic<bool> isPaused;
enum states { ABORT, LINKED, DISCO };
std::atomic<states> state;
std::string credentials;
bool zeroConf;
std::atomic<bool> flushed = false, notify = true;
int startOffset, volume = 0, bitrate = 160;
httpd_handle_t serverHandle;
@@ -50,6 +61,7 @@ private:
cspot_cmd_cb_t cmdHandler;
cspot_data_cb_t dataHandler;
std::string lastTrackId;
cspot::TrackInfo trackInfo;
std::shared_ptr<cspot::LoginBlob> blob;
std::unique_ptr<cspot::SpircHandler> spirc;
@@ -57,6 +69,7 @@ private:
void eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event);
void trackHandler(void);
size_t pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackId);
void enableZeroConf(void);
void runTask();
@@ -77,10 +90,27 @@ cspotPlayer::cspotPlayer(const char* name, httpd_handle_t server, int port, cspo
cJSON *item, *config = config_alloc_get_cjson("cspot_config");
if ((item = cJSON_GetObjectItem(config, "volume")) != NULL) volume = item->valueint;
if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint;
if ((item = cJSON_GetObjectItem(config, "bitrate")) != NULL) bitrate = item->valueint;
if ((item = cJSON_GetObjectItem(config, "deviceName") ) != NULL) this->name = item->valuestring;
else this->name = name;
cJSON_Delete(config);
else this->name = name;
if ((item = cJSON_GetObjectItem(config, "zeroConf")) != NULL) {
zeroConf = item->valueint;
cJSON_Delete(config);
} else {
zeroConf = true;
cJSON_AddNumberToObject(config, "zeroConf", 1);
config_set_cjson_str_and_free("cspot_config", config);
}
// get optional credentials from own NVS
if (!zeroConf) {
char *credentials = (char*) get_nvs_value_alloc_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, NULL);
if (credentials) {
this->credentials = credentials;
free(credentials);
}
}
if (bitrate != 96 && bitrate != 160 && bitrate != 320) bitrate = 160;
}
@@ -92,8 +122,7 @@ size_t cspotPlayer::pcmWrite(uint8_t *pcm, size_t bytes, std::string_view trackI
trackHandler();
}
dataHandler(pcm, bytes);
return bytes;
return dataHandler(pcm, bytes);
}
extern "C" {
@@ -179,11 +208,13 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
trackStatus = TRACK_INIT;
// memorize position for when track's beginning will be detected
startOffset = std::get<int>(event->data);
notify = !flushed;
flushed = false;
// Spotify servers do not send volume at connection
spirc->setRemoteVolume(volume);
cmdHandler(CSPOT_START, 44100);
CSPOT_LOG(info, "(re)start playing");
CSPOT_LOG(info, "(re)start playing at %d", startOffset);
break;
}
case cspot::SpircHandler::EventType::PLAY_PAUSE: {
@@ -192,23 +223,20 @@ void cspotPlayer::eventHandler(std::unique_ptr<cspot::SpircHandler::Event> event
break;
}
case cspot::SpircHandler::EventType::TRACK_INFO: {
auto trackInfo = std::get<cspot::TrackInfo>(event->data);
cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
spirc->updatePositionMs(startOffset);
startOffset = 0;
trackInfo = std::get<cspot::TrackInfo>(event->data);
break;
}
case cspot::SpircHandler::EventType::FLUSH:
flushed = true;
__attribute__ ((fallthrough));
case cspot::SpircHandler::EventType::NEXT:
case cspot::SpircHandler::EventType::PREV:
case cspot::SpircHandler::EventType::FLUSH: {
// FLUSH is sent when there is no next, just clean everything
case cspot::SpircHandler::EventType::PREV: {
cmdHandler(CSPOT_FLUSH);
break;
}
case cspot::SpircHandler::EventType::DISC:
cmdHandler(CSPOT_DISC);
isConnected = false;
state = DISCO;
break;
case cspot::SpircHandler::EventType::SEEK: {
cmdHandler(CSPOT_SEEK, std::get<int>(event->data));
@@ -266,7 +294,7 @@ void cspotPlayer::command(cspot_event_t event) {
* generate any cspot::event */
case CSPOT_DISC:
cmdHandler(CSPOT_DISC);
isConnected = false;
state = ABORT;
break;
// spirc->setRemoteVolume does not generate a cspot::event so call cmdHandler
case CSPOT_VOLUME_UP:
@@ -286,33 +314,48 @@ void cspotPlayer::command(cspot_event_t event) {
}
}
void cspotPlayer::runTask() {
void cspotPlayer::enableZeroConf(void) {
httpd_uri_t request = {
.uri = "/spotify_info",
.method = HTTP_GET,
.handler = ::handleGET,
.user_ctx = NULL,
};
};
// register GET and POST handler for built-in server
httpd_register_uri_handler(serverHandle, &request);
request.method = HTTP_POST;
request.handler = ::handlePOST;
httpd_register_uri_handler(serverHandle, &request);
// construct blob for that player
blob = std::make_unique<cspot::LoginBlob>(name);
CSPOT_LOG(info, "ZeroConf mode (port %d)", serverPort);
// Register mdns service, for spotify to find us
bell::MDNSService::registerService( blob->getDeviceName(), "_spotify-connect", "_tcp", "", serverPort,
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
{ {"VERSION", "1.0"}, {"CPath", "/spotify_info"}, {"Stack", "SP"} });
}
void cspotPlayer::runTask() {
bool useZeroConf = zeroConf;
// construct blob for that player
blob = std::make_unique<cspot::LoginBlob>(name);
CSPOT_LOG(info, "CSpot instance service name %s (id %s)", blob->getDeviceName().c_str(), blob->getDeviceId().c_str());
if (!zeroConf && !credentials.empty()) {
blob->loadJson(credentials);
CSPOT_LOG(info, "Reusable credentials mode");
} else {
// whether we want it or not we must use ZeroConf
useZeroConf = true;
enableZeroConf();
}
static int count = 0;
// gone with the wind...
while (1) {
clientConnected.wait();
CSPOT_LOG(info, "Spotify client connected for %s", name.c_str());
if (useZeroConf) clientConnected.wait();
CSPOT_LOG(info, "Spotify client launched for %s", name.c_str());
auto ctx = cspot::Context::createFromBlob(blob);
@@ -321,12 +364,26 @@ void cspotPlayer::runTask() {
else ctx->config.audioFormat = AudioFormat_OGG_VORBIS_160;
ctx->session->connectWithRandomAp();
auto token = ctx->session->authenticate(blob);
ctx->config.authData = ctx->session->authenticate(blob);
// Auth successful
if (token.size() > 0) {
if (ctx->config.authData.size() > 0) {
// we might have been forced to use zeroConf, so store credentials and reset zeroConf usage
if (!zeroConf) {
useZeroConf = false;
// can't call store_nvs... from a task running on EXTRAM stack
TimerHandle_t timer = xTimerCreate( "credentials", 1, pdFALSE, strdup(ctx->getCredentialsJson().c_str()),
[](TimerHandle_t xTimer) {
auto credentials = (char*) pvTimerGetTimerID(xTimer);
store_nvs_value_len_for_partition(NVS_DEFAULT_PART_NAME, spotify_ns.ns, NVS_TYPE_STR, spotify_ns.credentials, credentials, 0);
free(credentials);
xTimerDelete(xTimer, portMAX_DELAY);
} );
xTimerStart(timer, portMAX_DELAY);
}
spirc = std::make_unique<cspot::SpircHandler>(ctx);
isConnected = true;
state = LINKED;
// set call back to calculate a hash on trackId
spirc->getTrackPlayer()->setDataCallback(
@@ -347,7 +404,7 @@ void cspotPlayer::runTask() {
cmdHandler(CSPOT_VOLUME, volume);
// exit when player has stopped (received a DISC)
while (isConnected) {
while (state == LINKED) {
ctx->session->handlePacket();
// low-accuracy polling events
@@ -356,8 +413,13 @@ void cspotPlayer::runTask() {
uint32_t started;
cmdHandler(CSPOT_QUERY_STARTED, &started);
if (started) {
CSPOT_LOG(info, "next track's audio has reached DAC");
spirc->notifyAudioReachedPlayback();
CSPOT_LOG(info, "next track's audio has reached DAC (offset %d)", startOffset);
if (notify) spirc->notifyAudioReachedPlayback();
else notify = true;
cmdHandler(CSPOT_TRACK_INFO, trackInfo.duration, startOffset, trackInfo.artist.c_str(),
trackInfo.album.c_str(), trackInfo.name.c_str(), trackInfo.imageUrl.c_str());
spirc->updatePositionMs(startOffset);
startOffset = 0;
trackStatus = TRACK_STREAM;
}
} else if (trackStatus == TRACK_END) {
@@ -368,26 +430,35 @@ void cspotPlayer::runTask() {
CSPOT_LOG(info, "last track finished");
trackStatus = TRACK_INIT;
cmdHandler(CSPOT_STOP);
spirc->setPause(true);
spirc->notifyAudioEnded();
}
}
// on disconnect, stay in the core loop unless we are in ZeroConf mode
if (state == DISCO) {
// update volume then
cJSON *config = config_alloc_get_cjson("cspot_config");
cJSON_DeleteItemFromObject(config, "volume");
cJSON_AddNumberToObject(config, "volume", volume);
config_set_cjson_str_and_free("cspot_config", config);
// in ZeroConf mod, stay connected (in this loop)
if (!zeroConf) state = LINKED;
}
}
spirc->disconnect();
spirc.reset();
CSPOT_LOG(info, "disconnecting player %s", name.c_str());
} else {
CSPOT_LOG(error, "failed authentication, forcing ZeroConf");
if (!useZeroConf) enableZeroConf();
useZeroConf = true;
}
// we want to release memory ASAP and for sure
ctx.reset();
token.clear();
// update volume when we disconnect
cJSON *config = config_alloc_get_cjson("cspot_config");
cJSON_DeleteItemFromObject(config, "volume");
cJSON_AddNumberToObject(config, "volume", volume);
config_set_cjson_str_and_free("cspot_config", config);
ctx.reset();
}
}
@@ -396,6 +467,7 @@ void cspotPlayer::runTask() {
*/
struct cspot_s* cspot_create(const char *name, httpd_handle_t server, int port, cspot_cmd_cb_t cmd_cb, cspot_data_cb_t data_cb) {
bell::setDefaultLogger();
bell::enableTimestampLogging(true);
player = new cspotPlayer(name, server, port, cmd_cb, data_cb);
player->startTask();
return (cspot_s*) player;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ void bell::enableSubmoduleLogging() {
bell::bellGlobalLogger->enableSubmodule = true;
}
void bell::enableTimestampLogging() {
void bell::enableTimestampLogging(bool local) {
bell::bellGlobalLogger->enableTimestamp = true;
bell::bellGlobalLogger->shortTime = local;
}

View File

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

View File

@@ -14,6 +14,7 @@ class AbstractLogger {
public:
bool enableSubmodule = false;
bool enableTimestamp = false;
bool shortTime = false;
virtual void debug(std::string filename, int line, std::string submodule,
const char* format, ...) = 0;
@@ -94,10 +95,17 @@ class BellLogger : public bell::AbstractLogger {
now.time_since_epoch()) %
1000;
auto gmt_time = gmtime(&now_time);
printf(colorReset);
std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.'
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
struct tm* gmt_time;
if (shortTime) {
gmt_time = localtime(&now_time);
std::cout << std::put_time(gmt_time, "[%H:%M:%S") << '.'
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
} else {
gmt_time = gmtime(&now_time);
std::cout << std::put_time(gmt_time, "[%Y-%m-%d %H:%M:%S") << '.'
<< std::setfill('0') << std::setw(3) << nowMs.count() << "] ";
}
}
}
@@ -129,7 +137,7 @@ class BellLogger : public bell::AbstractLogger {
void setDefaultLogger();
void enableSubmoduleLogging();
void enableTimestampLogging();
void enableTimestampLogging(bool local = false);
} // namespace bell
#define BELL_LOG(type, ...) \

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
#include <variant> // for variant
#include <vector> // for vector
#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track...
#include "CDNAudioFile.h" // for CDNTrackStream, CDNTrackStream::Track...
#include "TrackQueue.h"
#include "protobuf/spirc.pb.h" // for MessageType
@@ -48,11 +48,12 @@ class SpircHandler {
void setPause(bool pause);
void previousSong();
bool previousSong();
void nextSong();
bool nextSong();
void notifyAudioReachedPlayback();
void notifyAudioEnded();
void updatePositionMs(uint32_t position);
void setRemoteVolume(int volume);
void loadTrackFromURI(const std::string& uri);
@@ -74,7 +75,7 @@ class SpircHandler {
void sendEvent(EventType type);
void sendEvent(EventType type, EventData data);
void skipSong(TrackQueue::SkipDirection dir);
bool skipSong(TrackQueue::SkipDirection dir);
void handleFrame(std::vector<uint8_t>& data);
void notify();
};

View File

@@ -32,8 +32,10 @@ struct TrackReference;
class TrackPlayer : bell::Task {
public:
// Callback types
typedef std::function<void(std::shared_ptr<QueuedTrack>)> TrackLoadedCallback;
typedef std::function<size_t(uint8_t*, size_t, std::string_view)> DataCallback;
typedef std::function<void(std::shared_ptr<QueuedTrack>, bool)>
TrackLoadedCallback;
typedef std::function<size_t(uint8_t*, size_t, std::string_view)>
DataCallback;
typedef std::function<void()> EOFCallback;
typedef std::function<bool()> isAiringCallback;
@@ -48,7 +50,7 @@ class TrackPlayer : bell::Task {
// CDNTrackStream::TrackInfo getCurrentTrackInfo();
void seekMs(size_t ms);
void resetState();
void resetState(bool paused = false);
// Vorbis codec callbacks
size_t _vorbisRead(void* ptr, size_t size, size_t nmemb);
@@ -88,6 +90,7 @@ class TrackPlayer : bell::Task {
std::atomic<bool> pendingReset = false;
std::atomic<bool> inFuture = false;
std::atomic<size_t> pendingSeekPositionMs = 0;
std::atomic<bool> startPaused = false;
std::mutex runningMutex;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,15 +31,17 @@ SpircHandler::SpircHandler(std::shared_ptr<cspot::Context> ctx) {
}
};
auto trackLoadedCallback = [this](std::shared_ptr<QueuedTrack> track) {
playbackState->setPlaybackState(PlaybackState::State::Playing);
auto trackLoadedCallback = [this](std::shared_ptr<QueuedTrack> track,
bool paused = false) {
playbackState->setPlaybackState(paused ? PlaybackState::State::Paused
: PlaybackState::State::Playing);
playbackState->updatePositionMs(track->requestedPosition);
this->notify();
// Send playback start event, unpause
sendEvent(EventType::PLAYBACK_START, (int) track->requestedPosition);
sendEvent(EventType::PLAY_PAUSE, false);
// Send playback start event, pause/unpause per request
sendEvent(EventType::PLAYBACK_START, (int)track->requestedPosition);
sendEvent(EventType::PLAY_PAUSE, paused);
};
this->ctx = ctx;
@@ -77,6 +79,12 @@ void SpircHandler::subscribeToMercury() {
void SpircHandler::loadTrackFromURI(const std::string& uri) {}
void SpircHandler::notifyAudioEnded() {
playbackState->updatePositionMs(0);
notify();
trackPlayer->resetState(true);
}
void SpircHandler::notifyAudioReachedPlayback() {
int offset = 0;
@@ -111,7 +119,7 @@ void SpircHandler::updatePositionMs(uint32_t position) {
void SpircHandler::disconnect() {
this->trackQueue->stopTask();
this->trackPlayer->resetState();
this->trackPlayer->stop();
this->ctx->session->disconnect();
}
@@ -142,7 +150,6 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
notify();
sendEvent(EventType::SEEK, (int)playbackState->remoteFrame.position);
//sendEvent(EventType::FLUSH);
break;
}
case MessageType_kMessageTypeVolume:
@@ -157,12 +164,14 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
setPause(false);
break;
case MessageType_kMessageTypeNext:
nextSong();
sendEvent(EventType::NEXT);
if (nextSong()) {
sendEvent(EventType::NEXT);
}
break;
case MessageType_kMessageTypePrev:
previousSong();
sendEvent(EventType::PREV);
if (previousSong()) {
sendEvent(EventType::PREV);
}
break;
case MessageType_kMessageTypeLoad: {
this->trackPlayer->start();
@@ -195,12 +204,17 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
CSPOT_LOG(debug, "Got replace frame");
playbackState->syncWithRemote();
trackQueue->updateTracks(playbackState->remoteFrame.state.position_ms,
false);
// 1st track is the current one, but update the position
trackQueue->updateTracks(
playbackState->remoteFrame.state.position_ms +
ctx->timeProvider->getSyncedTimestamp() -
playbackState->innerFrame.state.position_measured_at,
false);
this->notify();
trackPlayer->resetState();
sendEvent(EventType::FLUSH);
trackPlayer->resetState();
break;
}
case MessageType_kMessageTypeShuffle: {
@@ -227,34 +241,22 @@ void SpircHandler::notify() {
this->sendCmd(MessageType_kMessageTypeNotify);
}
void SpircHandler::skipSong(TrackQueue::SkipDirection dir) {
if (trackQueue->skipTrack(dir)) {
playbackState->setPlaybackState(PlaybackState::State::Playing);
notify();
bool SpircHandler::skipSong(TrackQueue::SkipDirection dir) {
bool skipped = trackQueue->skipTrack(dir);
// Reset track state
trackPlayer->resetState();
// Reset track state
trackPlayer->resetState(!skipped);
sendEvent(EventType::PLAY_PAUSE, false);
} else {
playbackState->setPlaybackState(PlaybackState::State::Paused);
playbackState->updatePositionMs(0);
notify();
sendEvent(EventType::PLAY_PAUSE, true);
}
notify();
sendEvent(EventType::FLUSH);
// send NEXT or PREV event only when successful
return skipped;
}
void SpircHandler::nextSong() {
skipSong(TrackQueue::SkipDirection::NEXT);
bool SpircHandler::nextSong() {
return skipSong(TrackQueue::SkipDirection::NEXT);
}
void SpircHandler::previousSong() {
skipSong(TrackQueue::SkipDirection::PREV);
bool SpircHandler::previousSong() {
return skipSong(TrackQueue::SkipDirection::PREV);
}
std::shared_ptr<TrackPlayer> SpircHandler::getTrackPlayer() {

View File

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

View File

@@ -13,8 +13,10 @@
#include "WrappedSemaphore.h" // for WrappedSemaphore
#ifdef BELL_VORBIS_FLOAT
#define VORBIS_SEEK(file, position) (ov_time_seek(file, (double)position / 1000))
#define VORBIS_READ(file, buffer, bufferSize, section) (ov_read(file, buffer, bufferSize, 0, 2, 1, section))
#define VORBIS_SEEK(file, position) \
(ov_time_seek(file, (double)position / 1000))
#define VORBIS_READ(file, buffer, bufferSize, section) \
(ov_read(file, buffer, bufferSize, 0, 2, 1, section))
#else
#define VORBIS_SEEK(file, position) (ov_time_seek(file, position))
#define VORBIS_READ(file, buffer, bufferSize, section) \
@@ -68,6 +70,7 @@ TrackPlayer::TrackPlayer(std::shared_ptr<cspot::Context> ctx,
TrackPlayer::~TrackPlayer() {
isRunning = false;
resetState();
std::scoped_lock lock(runningMutex);
}
@@ -84,10 +87,11 @@ void TrackPlayer::stop() {
std::scoped_lock lock(runningMutex);
}
void TrackPlayer::resetState() {
void TrackPlayer::resetState(bool paused) {
// Mark for reset
this->pendingReset = true;
this->currentSongPlaying = false;
this->startPaused = paused;
std::scoped_lock lock(dataOutMutex);
@@ -116,7 +120,7 @@ void TrackPlayer::runTask() {
while (isRunning) {
// Ensure we even have any tracks to play
if (!this->trackQueue->hasTracks() ||
(endOfQueueReached && trackQueue->isFinished())) {
(!pendingReset && endOfQueueReached && trackQueue->isFinished())) {
this->trackQueue->playableSemaphore->twait(300);
continue;
}
@@ -181,7 +185,8 @@ void TrackPlayer::runTask() {
}
if (trackOffset == 0 && pendingSeekPositionMs == 0) {
this->trackLoaded(track);
this->trackLoaded(track, startPaused);
startPaused = false;
}
int32_t r =
@@ -233,8 +238,8 @@ void TrackPlayer::runTask() {
if (!currentSongPlaying || pendingReset)
break;
written =
dataCallback(pcmBuffer.data() + (ret - toWrite), toWrite, track->identifier);
written = dataCallback(pcmBuffer.data() + (ret - toWrite),
toWrite, track->identifier);
}
if (written == 0) {
BELL_SLEEP_MS(50);

View File

@@ -504,11 +504,18 @@ void TrackQueue::processTrack(std::shared_ptr<QueuedTrack> track) {
bool TrackQueue::queueNextTrack(int offset, uint32_t positionMs) {
const int requestedRefIndex = offset + currentTracksIndex;
if (requestedRefIndex < 0 || requestedRefIndex >= currentTracks.size()) {
return false;
}
if (offset < 0) {
// in case we re-queue current track, make sure position is updated (0)
if (offset == 0 && preloadedTracks.size() &&
preloadedTracks[0]->ref == currentTracks[currentTracksIndex]) {
preloadedTracks.pop_front();
}
if (offset <= 0) {
preloadedTracks.push_front(std::make_shared<QueuedTrack>(
currentTracks[requestedRefIndex], ctx, positionMs));
} else {
@@ -520,13 +527,30 @@ bool TrackQueue::queueNextTrack(int offset, uint32_t positionMs) {
}
bool TrackQueue::skipTrack(SkipDirection dir, bool expectNotify) {
bool canSkipNext = currentTracks.size() > currentTracksIndex + 1;
bool canSkipPrev = currentTracksIndex > 0;
bool skipped = true;
std::scoped_lock lock(tracksMutex);
if ((dir == SkipDirection::NEXT && canSkipNext) ||
(dir == SkipDirection::PREV && canSkipPrev)) {
std::scoped_lock lock(tracksMutex);
if (dir == SkipDirection::NEXT) {
if (dir == SkipDirection::PREV) {
uint64_t position =
!playbackState->innerFrame.state.has_position_ms
? 0
: playbackState->innerFrame.state.position_ms +
ctx->timeProvider->getSyncedTimestamp() -
playbackState->innerFrame.state.position_measured_at;
if (currentTracksIndex > 0 && position < 3000) {
queueNextTrack(-1);
if (preloadedTracks.size() > MAX_TRACKS_PRELOAD) {
preloadedTracks.pop_back();
}
currentTracksIndex--;
} else {
queueNextTrack(0);
}
} else {
if (currentTracks.size() > currentTracksIndex + 1) {
preloadedTracks.pop_front();
if (!queueNextTrack(preloadedTracks.size() + 1)) {
@@ -535,15 +559,11 @@ bool TrackQueue::skipTrack(SkipDirection dir, bool expectNotify) {
currentTracksIndex++;
} else {
queueNextTrack(-1);
if (preloadedTracks.size() > MAX_TRACKS_PRELOAD) {
preloadedTracks.pop_back();
}
currentTracksIndex--;
skipped = false;
}
}
if (skipped) {
// Update frame data
playbackState->innerFrame.state.playing_track_index = currentTracksIndex;
@@ -551,11 +571,9 @@ bool TrackQueue::skipTrack(SkipDirection dir, bool expectNotify) {
// Reset position to zero
notifyPending = true;
}
return true;
}
return false;
return skipped;
}
bool TrackQueue::hasTracks() {

View File

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

View File

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

View File

@@ -136,10 +136,8 @@ static bool cmd_handler(cspot_event_t event, ...) {
displayer_timer(DISPLAYER_ELAPSED, va_arg(args, int), -1);
break;
case CSPOT_TRACK_INFO: {
uint32_t duration = va_arg(args, int);
uint32_t offset = va_arg(args, int);
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*);
char *artwork = va_arg(args, char*);
uint32_t duration = va_arg(args, int), offset = va_arg(args, int);
char *artist = va_arg(args, char*), *album = va_arg(args, char*), *title = va_arg(args, char*), *artwork = va_arg(args, char*);
if (artwork && displayer_can_artwork()) {
ESP_LOGI(TAG, "requesting artwork %s", artwork);
http_download(artwork, 128*1024, got_artwork, NULL);
@@ -163,15 +161,11 @@ static bool cmd_handler(cspot_event_t event, ...) {
*/
static void cspot_sink_start(nm_state_t state_id, int sub_state) {
const char *hostname;
uint8_t mac[6];
cmd_handler_chain = cspot_cbs.cmd;
network_get_hostname(&hostname);
esp_netif_get_mac(network_get_active_interface(), mac);
for (int i = 0; i < 6; i++) sprintf(deviceId + 2*i, "%02x", mac[i]);
ESP_LOGI(TAG, "Starting Spotify (CSpot) servicename %s with id %s", hostname, deviceId);
ESP_LOGI(TAG, "starting Spotify on host %s", hostname);
int port;
httpd_handle_t server = http_get_server(&port);

View File

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

Some files were not shown because too many files have changed in this diff Show More