Compare commits

...

16 Commits

Author SHA1 Message Date
Philippe G
3df6568b6a dual vertical VU - release 2020-08-19 00:25:51 -07:00
Philippe G
ca97b8045e GPIO 7 might be used by error - release 2020-08-18 18:11:33 -07:00
Philippe G
5e8a3fd755 better synchronize bass/treble & equalizer
remove large fonts - release

finalize equalizer/tone interaction
2020-08-17 21:01:57 -07:00
philippe44
92fffb7635 Update README.md 2020-08-15 23:44:58 -07:00
philippe44
5e372860c8 Update README.md 2020-08-15 23:43:27 -07:00
philippe44
36c3f0eb49 Update README.md 2020-08-15 23:38:45 -07:00
Philippe G
4ecc9da6d3 take prefs from the correct namespace 2020-08-15 19:25:27 -07:00
Philippe G
45e0dd38df update this $%*!§* plugin for good, this time 2020-08-15 19:09:23 -07:00
Philippe G
3922851129 update plugin! 2020-08-15 19:07:47 -07:00
Philippe G
f08bde8d48 headset, bass/treble, battery from LMS - release 2020-08-15 18:45:59 -07:00
philippe44
a6b57604d3 Update README.md 2020-08-15 16:05:16 -07:00
Philippe G
ca33ff4ba9 fix DAC defaults in KConfig 2020-08-14 10:56:39 -07:00
Philippe G
8b55fa3986 DAC I2S default were wrong - release 2020-08-14 09:23:53 -07:00
Philippe G
e04a631665 typo in config - release 2020-08-13 14:28:11 -07:00
Philippe G
bcb087f9ee warning-free compile (prepare archiving) - release 2020-08-12 16:31:47 -07:00
Philippe G
c5d871f5ee display inline renaming - release 2020-08-11 16:27:58 -07:00
36 changed files with 516 additions and 237 deletions

134
README.md
View File

@@ -86,6 +86,7 @@ The parameter "dac_controlset" allows definition of simple commands to be sent o
```
This is standard JSON notation, so if you are not familiar with it, Google is your best friend. Be aware that the '...' means you can have as many entries as you want, it's not part of the syntax. Every section is optional, but it does not make sense to set i2c in the 'dac_config' parameter and not setting anything here. The parameter 'mode' allows to *or* the register with the value or to *and* it. Don't set 'mode' if you simply want to write. **Note that all values must be decimal**
NB: For well-known configuration, this is ignored
### SPDIF
The NVS parameter "spdif_config" sets the i2s's gpio needed for SPDIF.
@@ -97,6 +98,7 @@ Leave it blank to disable SPDIF usage, you can also define them at compile time
```
bck=<gpio>,ws=<gpio>,do=<gpio>
```
NB: For well-known configuration, this is ignored
### Display
The NVS parameter "display_config" sets the parameters for an optional display. Syntax is
```
@@ -156,12 +158,13 @@ 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>]
```
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for SqueezeAMP where these are forced at runtime.
You can define the defaults for jack, spkfault leds at compile time but nvs parameter takes precedence except for well-known configurations where these are forced at runtime.
### LED
See §**set_GPIO** for how to set the green and red LEDs. In addition, their brightness can be controlled using the "led_brigthness" parameter. The syntax is
```
[green=0..100][,red=0..100]
```
NB: For well-known configuration, this is ignored
### Rotary Encoder
One rotary encoder is supported, quadrature shift with press. Such encoders usually have 2 pins for encoders (A and B), and common C that must be set to ground and an optional SW pin for press. A, B and SW must be pulled up, so automatic pull-up is provided by ESP32, but you can add your own resistors. A bit of filtering on A and B (~470nF) helps for debouncing which is not made by software.
@@ -278,12 +281,67 @@ There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctr
### Battery / ADC
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. 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>
channel=0..7,scale=<scale>,cells=<2|3>
```
NB: Set parameter to empty to disable battery reading
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
# 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".
- Once connected, navigate to 192.168.4.1
- Wait for the list of access points visible from the device to populate in the web page.
- Choose an access point and enter any credential as needed
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
## Setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, choose the mode in which you want squeezelite to start
- Generate the command
- Add or change any additional command line option (for example player name, etc)
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
- Update the configuration
- click on the "start toggle" button. This will force a reboot.
- 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'
## 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.
## Update Squeezelite
- From the firmware tab, click on "Check for Updates"
- Look for updated binaries
- Select a line
- Click on "Flash!"
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
- You can choose a local file or have a local webserver
## Recovery
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
## Additional configuration notes (from the Web UI)
The squeezelite options are very similar to the regular Linux ones. Differences are :
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
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
- 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
# Building everything yourself
## Setting up ESP-IDF
### Docker
You can use docker to build squeezelite-esp32
You can use docker to build squeezelite-esp32 (optional)
First you need to build the Docker container:
```
docker build -t esp-idf .
@@ -301,9 +359,11 @@ If you want to use a more recent version of gcc and IDF (4.0 stable), move to cm
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/
And then copying the i2s.c patch file from this repo over to the esp-idf folder
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264
You also need to use esp-dsp recent version or at least make sure you have this patch https://github.com/espressif/esp-dsp/pull/12/commits/8b082c1071497d49346ee6ed55351470c1cb4264. As of this writing (08.2020), espressif has patched esp-dsp so this is no more needed
## Building Squeezelite-esp32
Don't forget the 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)
### Usng make (deprecated)
MOST IMPORTANT: create the right default config file
- make defconfig
(Note: You can also copy over config files from the build-scripts folder to ./sdkconfig)
@@ -334,55 +394,13 @@ You can also manually download the recovery & initial boot
```
python ${IDF_PATH}/components/esptool_py/esptool/esptool.py --chip esp32 --port ${ESPPORT} --baud ${ESPBAUD} --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0xd000 ./build/ota_data_initial.bin 0x1000 ./build/bootloader/bootloader.bin 0x10000 ./build/recovery.bin 0x8000 ./build/partitions.bin
```
# Configuration
1/ 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".
- Once connected, navigate to 192.168.4.1
- Wait for the list of access points visible from the device to populate in the web page.
- Choose an access point and enter any credential as needed
- Once connection is established, note down the address the device received; this is the address you will use to configure it going forward
2/ setup squeezelite command line (optional)
At this point, the device should have disabled its built-in access point and should be connected to a known WiFi network.
- navigate to the address that was noted in step #1
- Using the list of predefined options, hoose the mode in which you want squeezelite to start
- Generate the command
- Add or change any additional command line option (for example player name, etc)
- Activate squeezelite execution: this tells the device to automatiaclly run the command at start
- Update the configuration
- click on the "start toggle" button. This will force a reboot.
- The toggle switch should be set to 'ON' to ensure that squeezelite is active after booting
3/ Updating Squeezelite
- From the firmware tab, click on "Check for Updates"
- Look for updated binaries
- Select a line
- Click on "Flash!"
- The system will reboot into recovery mode (if not already in that mode), wipe the squeezelite partition and download/flash the selected version
3/ Recovery
- From the firmware tab, click on the "Recovery" button. This will reboot the ESP32 into recovery, where additional configuration options are available from the NVS editor
# Additional command line notes, configured from the http configuration
The squeezelite options are very similar to the regular Linux ones. Differences are :
- the output is -o ["BT -n '<sinkname>' "] | [I2S]
- if you've compiled with RESAMPLE option, normal soxr options are available using -R [-u <options>]. Note that anything above LQ or MQ will overload the CPU
- if you've used RESAMPLE16, <options> are (b|l|m)[:i], with b = basic linear interpolation, l = 13 taps, m = 21 taps, i = interpolate filter coefficients
For example, so use a BT speaker named MySpeaker, accept audio up to 192kHz and resample everything to 44100 and use 16 bits resample with medium quality, the command line is:
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
- Z <rate> : tell LMS what is the max sample rate supported before LMS resamples
- R (see above)
- r "<minrate>-<maxrate>"
## Additional misc notes to do you build
### Using cmake
Create you config using 'idf.py menuconfig' then build binaries using 'idf.py all'. It will build the recovery and the application (squeezelite) itself. See the recommended command to upload everything. Otherwise, if you just want to download squeezelite, do
```
python.exe <idf_path>\components\esptool_py\esptool\esptool.py -p COM<n> -b 921600 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x150000 build\squeezelite.bin
```
Use 'idf monitor' to monitor the application (see esp-idf documentation)
## Additional misc notes to do you build (kitchen sink)
- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
- for codecs libraries, add -mlongcalls if you want to rebuild them, but you should not (use the provided ones in codecs/lib). if you really want to rebuild them, open an issue
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work
@@ -393,16 +411,14 @@ See squeezlite command line, but keys options are
#pragma GCC push_options
#pragma GCC optimize ("O0")
#pragma GCC pop_options
- better use helixaac
- opus & opusfile
- for opus, the ESP-provided library seems to work, but opusfile is still needed
- per mad & few others, edit configure and change $ac_link to add -c (faking link)
- change ac_files to remove ''
- add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required
- stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h
- better use helixaac
- set IDF_PATH=/home/esp-idf
- set ESPPORT=COM9
- update flash partition size
- other compiler #define
- use no resampling or set RESAMPLE (soxr) or set RESAMPLE16 for fast fixed 16 bits resampling
- use LOOPBACK (mandatory)
@@ -410,10 +426,8 @@ See squeezlite command line, but keys options are
- LINKALL (mandatory)
- NO_FAAD unless you want to us faad, which currently overloads the CPU
- TREMOR_ONLY (mandatory)
- better use helixaac
- When initially cloning the repo, make sure you do it recursively. For example:
- git clone --recursive https://github.com/sle118/squeezelite-esp32.git
- If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location
- git submodule update --init --recursive

View File

@@ -26,7 +26,7 @@ CONFIG_SPKFAULT_GPIO=-1
CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=-1
CONFIG_BAT_SCALE="0"
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_BCK_IO=27
CONFIG_SPDIF_WS_IO=26
CONFIG_SPDIF_DO_IO=-1
@@ -116,10 +116,6 @@ CONFIG_INCLUDE_VORBIS=y
CONFIG_INCLUDE_ALAC=y
CONFIG_A1S=y
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
CONFIG_A2DP_DEV_NAME="Squeezelite"

View File

@@ -25,13 +25,13 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=-1
CONFIG_BAT_SCALE="0"
CONFIG_I2S_NUM=0
CONFIG_I2S_BCK_IO=33
CONFIG_I2S_WS_IO=25
CONFIG_I2S_DO_IO=32
CONFIG_I2S_BCK_IO=-1
CONFIG_I2S_WS_IO=-1
CONFIG_I2S_DO_IO=-1
CONFIG_I2S_DI_IO=-1
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=33
CONFIG_SPDIF_WS_IO=25
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_MUTE_GPIO=-1
CONFIG_MUTE_GPIO_LEVEL=-1
@@ -121,11 +121,6 @@ CONFIG_INCLUDE_ALAC=y
CONFIG_BASIC_I2C_BT=y
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_BCK_IO=-1
CONFIG_SPDIF_WS_IO=-1
CONFIG_SPDIF_DO_IO=-1
CONFIG_A2DP_SINK_NAME="SMSL BT4.2"
CONFIG_A2DP_DEV_NAME="Squeezelite"
CONFIG_A2DP_CONTROL_DELAY_MS=500

View File

@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14:0"
CONFIG_IDF_TARGET_ESP32=y

View File

@@ -29,7 +29,7 @@ CONFIG_SPKFAULT_GPIO_LEVEL=0
CONFIG_BAT_CHANNEL=7
CONFIG_BAT_SCALE="20.24"
CONFIG_I2S_NUM=0
CONFIG_SDIF_NUM=0
CONFIG_SPDIF_NUM=0
CONFIG_SPDIF_CONFIG="bck=33,ws=25,do=15"
CONFIG_DAC_CONFIG="model=TAS57xx,bck=33,ws=25,do=32,sda=27,scl=26,mute=14"
CONFIG_MUTE_GPIO_LEVEL=-1

View File

@@ -168,7 +168,7 @@ static void Update1( struct GDS_Device* Device ) {
}
// in 1 bit mode, SSD1326 has a different memory map than SSD1306 and SH1106
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static void IRAM_ATTR _DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t XBit = ( X & 0x07 );
uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width + X ) >> 3 );
@@ -188,12 +188,12 @@ static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int
for (int r = y1; r <= y2; r++) {
int c = x1;
// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
while (c & 0x07 && c <= x2) _DrawPixel1Fast( Device, c++, r, Color );
// at this point we are aligned on column boundary
int chunk = (x2 - c + 1) >> 3;
memset(optr + Width * r + (c >> 3), _Color, chunk );
c += chunk * 8;
while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
while (c <= x2) _DrawPixel1Fast( Device, c++, r, Color );
}
}
@@ -320,7 +320,7 @@ static const struct GDS_Device SSD132x = {
struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
uint8_t Model;
int Depth;
if (strcasestr(Driver, "SSD1326")) Model = SSD1326;
else if (strcasestr(Driver, "SSD1327")) Model = SSD1327;
else return NULL;
@@ -328,13 +328,14 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = SSD132x;
((struct PrivateSpace*) Device->Private)->Model = Model;
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
Private->Model = Model;
sscanf(Driver, "%*[^:]:%u", &Depth);
if (Model == SSD1326 && Depth == 1) {
Device->Update = Update1;
Device->DrawPixelFast = DrawPixel1Fast;
Device->DrawPixelFast = _DrawPixel1Fast;
Device->DrawBitmapCBR = DrawBitmapCBR;
Device->ClearWindow = ClearWindow;
Device->Depth = 1;

View File

@@ -118,7 +118,7 @@ static void Update( struct GDS_Device* Device ) {
}
// remember that for these ELD drivers W and H are "inverted"
static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void _DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
Y>>= 3;
@@ -129,7 +129,7 @@ static void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, in
static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
for (int r = y1; r <= y2; r++) {
for (int c = x1; c <= x2; c++) {
DrawPixelFast( Device, c, r, Color );
_DrawPixel( Device, c, r, Color );
}
}
}
@@ -228,7 +228,7 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SSD1675 = {
.DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow,
.DrawPixelFast = DrawPixelFast,
.DrawPixelFast = _DrawPixel,
.Update = Update, .Init = Init,
.Mode = GDS_MONO, .Depth = 1,
.Alloc = GDS_ALLOC_NONE,

View File

@@ -282,8 +282,9 @@ struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
*Device = ST77xx;
((struct PrivateSpace*) Device->Private)->Model = Model;
sscanf(Driver, "%*[^:]:%u", &Depth);
struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
Private->Model = Model;
if (Depth == 18) {
Device->Mode = GDS_RGB666;

View File

@@ -103,7 +103,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1;
// for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) {
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
r++;
}
// go fast if we have more than 8 lines to write
@@ -111,7 +111,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8;
} else while (r <= y2) {
for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
r++;
}
}
@@ -127,10 +127,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
// try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) {
int c = x1;
if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color);
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
}
}
} else if (Device->Depth == 8) {
@@ -142,7 +142,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
} else {
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
GDS_DrawPixelFast( Device, x, y, Color);
DrawPixelFast( Device, x, y, Color);
}
}
}

View File

@@ -5,7 +5,6 @@
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include <stdio.h>
#include <string.h>
#include <stdint.h>
@@ -45,9 +44,13 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
*a = Temp;
}
// un-comment if need to be instanciated for external callers
extern inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
extern inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixelFast( Device, X, Y, Color );
}
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
DrawPixel( Device, X, Y, Color );
}
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
int XEnd = x + Width;
@@ -60,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
if (y < 0) y = 0;
else if (y >= Device->Height) y = Device->Height - 1;
for ( ; x < XEnd; x++ ) GDS_DrawPixelFast( Device, x, y, Color );
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
}
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
@@ -74,7 +77,7 @@ void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Col
if (y < 0) y = 0;
else if (YEnd >= Device->Height) YEnd = Device->Height - 1;
for ( ; y < YEnd; y++ ) GDS_DrawPixel( Device, x, y, Color );
for ( ; y < YEnd; y++ ) DrawPixel( Device, x, y, Color );
}
static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
@@ -94,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; x < x1; x++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -123,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; y < y1; y++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
if ( Error > 0 ) {
@@ -317,32 +320,32 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
// don't know bitdepth, use brute-force solution
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = *Data++;
GDS_DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
GDS_DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
if (++r == Height) { c++; r = 0; }
}
/* for better understanding, here is the mundane version
for (int x = 0; x < Width; x++) {
for (int y = 0; y < Height; y++) {
uint8_t Byte = *Data++;
GDS_DrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
GDS_DrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
GDSDrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
}
}
*/
}
Device->Dirty = true;
}
}

View File

@@ -17,15 +17,12 @@
extern "C" {
#endif
#ifndef _GDS_PRIVATE_H_
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
#endif
void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0
void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color);

View File

@@ -89,7 +89,7 @@ void GDS_FontDrawChar( struct GDS_Device* Device, char Character, int x, int y,
YBit = ( i + OffsetY ) & 0x07;
if ( GlyphData[ YByte ] & BIT( YBit ) ) {
GDS_DrawPixel( Device, x, y, Color );
DrawPixel( Device, x, y, Color );
}
}

View File

@@ -133,7 +133,7 @@ static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
if (y < Context->YMin) continue; \
for (int x = Frame->left; x <= Frame->right; x++) { \
if (x < Context->XMin) continue; \
GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \
DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \
Pixels += 3; \
} \
}
@@ -274,13 +274,13 @@ static inline int ToSelf(uint8_t **Pixel) {
if (Scale > 0) { \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
GDS_DrawPixel( Device, c + x, r + y, F(S) >> Scale); \
DrawPixel( Device, c + x, r + y, F(S) >> Scale); \
} \
} \
} else { \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
GDS_DrawPixel( Device, c + x, r + y, F(S) << -Scale); \
DrawPixel( Device, c + x, r + y, F(S) << -Scale); \
} \
} \
}
@@ -289,7 +289,7 @@ static inline int ToSelf(uint8_t **Pixel) {
T *S = (T*) Image; \
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
GDS_DrawPixel(Device, c + x, r + y, *S++); \
DrawPixel(Device, c + x, r + y, *S++); \
} \
}
@@ -298,7 +298,7 @@ static inline int ToSelf(uint8_t **Pixel) {
for (int r = 0; r < Height; r++) { \
for (int c = 0; c < Width; c++) { \
uint32_t v = *S++; v |= *S++ << 8; v |= *S++ << 16; \
GDS_DrawPixel(Device, c + x, r + y, v); \
DrawPixel(Device, c + x, r + y, v); \
} \
}

View File

@@ -136,7 +136,7 @@ struct GDS_Device {
bool GDS_Reset( struct GDS_Device* Device );
bool GDS_Init( struct GDS_Device* Device );
inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
bool Result = (
( x >= 0 ) &&
( x < Device->Width ) &&
@@ -153,7 +153,7 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result;
}
inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint32_t YBit = ( Y & 0x07 );
uint8_t* FBOffset;
@@ -174,48 +174,48 @@ inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int
}
}
inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
}
inline void IRAM_ATTR GDS_DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
Device->Framebuffer[Y * Device->Width + X] = Color;
}
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
inline void IRAM_ATTR GDS_DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
*FBOffset = __builtin_bswap16(Color);
}
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
inline void IRAM_ATTR GDS_DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
}
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
inline void IRAM_ATTR GDS_DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
}
inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color );
else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color );
else if (Device->Depth == 16) GDS_DrawPixel16Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) GDS_DrawPixel18Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) GDS_DrawPixel24Fast( Device, X, Y, Color );
else if (Device->Depth == 8) GDS_DrawPixel8Fast( Device, X, Y, Color );
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
}
inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) {
GDS_DrawPixelFast( Device, x, y, Color );
DrawPixelFast( Device, x, y, Color );
}
}

View File

@@ -35,12 +35,20 @@ static const struct GDS_FontDef *GuessFont( struct GDS_Device *Device, int FontT
case GDS_FONT_MEDIUM:
default:
return &Font_droid_sans_fallback_15x17;
#ifdef USE_LARGE_FONTS
case GDS_FONT_LARGE:
return &Font_droid_sans_fallback_24x28;
break;
case GDS_FONT_SEGMENT:
if (Device->Height == 32) return &Font_Tarable7Seg_16x32;
else return &Font_Tarable7Seg_32x64;
#else
case GDS_FONT_LARGE:
case GDS_FONT_SEGMENT:
ESP_LOGW(TAG, "large fonts disabled");
return &Font_droid_sans_fallback_15x17;
break;
#endif
}
}
@@ -99,7 +107,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->Width; c++)
for (int y = Y_min; y < Y_max; y++)
GDS_DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
}
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

View File

@@ -89,7 +89,13 @@ extern struct mdnsd* glmDNSServer;
extern log_level raop_loglevel;
static log_level *loglevel = &raop_loglevel;
#ifdef WIN32
static void* rtsp_thread(void *arg);
static void* search_remote(void *args);
#else
static void rtsp_thread(void *arg);
static void search_remote(void *args);
#endif
static void cleanup_rtsp(raop_ctx_t *ctx, bool abort);
static bool handle_rtsp(raop_ctx_t *ctx, int sock);
@@ -97,7 +103,7 @@ static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
static int base64_pad(char *src, char **padded);
static int base64_encode(const void *data, int size, char **str);
static int base64_decode(const char *str, void *data);
static void* search_remote(void *args);
extern char private_key[];
@@ -352,7 +358,11 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
}
free(command);
closesocket(sock);
return true;
}
/*----------------------------------------------------------------------------*/
#ifdef WIN32
static void *rtsp_thread(void *arg) {
@@ -397,9 +407,9 @@ static void *rtsp_thread(void *arg) {
sock = -1;
}
}
if (sock != -1) closesocket(sock);
if (sock != -1) closesocket(sock);
#ifndef WIN32
xTaskNotifyGive(ctx->joiner);
vTaskSuspend(NULL);
@@ -708,7 +718,7 @@ static void* search_remote(void *args) {
query_mDNS(ctx->active_remote.handle, "_dacp._tcp.local", 0, 0, &search_remote_cb, (void*) ctx);
return NULL;
return NULL;
}
#else
@@ -741,8 +751,6 @@ static void* search_remote(void *args) {
}
}
mdns_query_results_free(results);
}
mdns_query_results_free(results);
}

View File

@@ -158,8 +158,12 @@ static void buffer_reset(abuf_t *audio_buffer);
static void buffer_release(abuf_t *audio_buffer);
static void buffer_reset(abuf_t *audio_buffer);
static void buffer_push_packet(rtp_t *ctx);
static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last);
static bool rtp_request_timing(rtp_t *ctx);
static int seq_order(seq_t a, seq_t b);
#ifdef WIN32
static void *rtp_thread_func(void *arg);
#else
static void rtp_thread_func(void *arg);
#endif
@@ -565,7 +569,11 @@ static void buffer_push_packet(rtp_t *ctx) {
}
}
/*---------------------------------------------------------------------------*/
#ifdef WIN32
static void *rtp_thread_func(void *arg) {
#else
static void rtp_thread_func(void *arg) {
#endif
@@ -620,8 +628,9 @@ static void *rtp_thread_func(void *arg) {
unsigned rtptime;
// re-sent packet
case 0x56: {
pktp += 4;
case 0x56: {
pktp += 4;
plen -= 4;
}
// fall through
@@ -695,7 +704,6 @@ static void *rtp_thread_func(void *arg) {
break;
}
// NTP timing packet
case 0x53: {
@@ -712,8 +720,8 @@ static void *rtp_thread_func(void *arg) {
}
/*
The expected elapsed remote time should be exactly the same as
elapsed local time between the two request, corrected by the
elapsed local time between the two request, corrected by the
drifting
u64_t expected = ctx->timing.remote + MS2NTP(reference - ctx->timing.local);
*/
@@ -740,9 +748,9 @@ static void *rtp_thread_func(void *arg) {
free(packet);
LOG_INFO("[%p]: terminating", ctx);
#ifndef WIN32
xTaskNotifyGive(ctx->joiner);
#ifndef WIN32
xTaskNotifyGive(ctx->joiner);
vTaskSuspend(NULL);
#else
return NULL;
#endif

View File

@@ -33,15 +33,27 @@ static struct {
int channel;
float sum, avg, scale;
int count;
int cells;
TimerHandle_t timer;
} battery;
} battery = {
.channel = CONFIG_BAT_CHANNEL,
.cells = 2,
};
/****************************************************************************************
*
*/
int battery_value_svc(void) {
return battery.avg;
return battery.avg;
}
/****************************************************************************************
*
*/
uint8_t battery_level_svc(void) {
// TODO: this is totally incorrect
return battery.avg ? (battery.avg - (3.0 * battery.cells)) / ((4.2 - 3.0) * battery.cells) * 100 : 0;
}
/****************************************************************************************
*
@@ -59,29 +71,30 @@ static void battery_callback(TimerHandle_t xTimer) {
*
*/
void battery_svc_init(void) {
battery.channel = CONFIG_BAT_CHANNEL;
#ifdef CONFIG_BAT_SCALE
battery.scale = atof(CONFIG_BAT_SCALE);
#endif
#ifndef CONFIG_BAT_LOCKED
char *nvs_item = config_alloc_get_default(NVS_TYPE_STR, "bat_config", "n", 0);
if (nvs_item) {
char *p;
char *p;
#ifndef CONFIG_BAT_LOCKED
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
#endif
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
free(nvs_item);
}
#endif
if (battery.channel != -1) {
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f", battery.channel, battery.scale);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(battery.channel, ADC_ATTEN_DB_0);
battery.avg = adc1_get_raw(battery.channel) * battery.scale / 4095.0;
battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
xTimerStart(battery.timer, portMAX_DELAY);
ESP_LOGI(TAG, "Battery measure channel: %u, scale %f, cells %u, avg %.2fV", battery.channel, battery.scale, battery.cells, battery.avg);
} else {
ESP_LOGI(TAG, "No battery");
}

View File

@@ -17,4 +17,5 @@ extern void (*spkfault_handler_svc)(bool inserted);
extern bool spkfault_svc(void);
extern int battery_value_svc(void);
extern uint8_t battery_level_svc(void);

View File

@@ -63,7 +63,6 @@ static void ac101_start(ac_module_t mode);
static void ac101_stop(void);
static void ac101_set_earph_volume(uint8_t volume);
static void ac101_set_spk_volume(uint8_t volume);
static int ac101_get_spk_volume(void);
static int i2c_port;
@@ -268,13 +267,6 @@ void set_sample_rate(int rate) {
i2c_write_reg(I2S_SR_CTRL, rate);
}
/****************************************************************************************
* Get normalized (0..100) speaker volume
*/
static int ac101_get_spk_volume(void) {
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
}
/****************************************************************************************
* Set normalized (0..100) volume
*/
@@ -285,13 +277,6 @@ static void ac101_set_spk_volume(uint8_t volume) {
i2c_write_reg(SPKOUT_CTRL, value);
}
/****************************************************************************************
* Get normalized (0..100) earphone volume
*/
static int ac101_get_earph_volume(void) {
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
}
/****************************************************************************************
* Set normalized (0..100) earphone volume
*/
@@ -302,6 +287,21 @@ static void ac101_set_earph_volume(uint8_t volume) {
i2c_write_reg(HPOUT_CTRL, value);
}
#if 0
/****************************************************************************************
* Get normalized (0..100) speaker volume
*/
static int ac101_get_spk_volume(void) {
return ((i2c_read_reg(SPKOUT_CTRL) & 0x1f) * 100) / 0x1f;
}
/****************************************************************************************
* Get normalized (0..100) earphone volume
*/
static int ac101_get_earph_volume(void) {
return (((i2c_read_reg(HPOUT_CTRL) >> 4) & 0x3f) * 100) / 0x3f;
}
/****************************************************************************************
*
*/
@@ -330,6 +330,27 @@ static void ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mi
i2c_write_reg(OMIXER_BST1_CTRL,regval);
}
/****************************************************************************************
*
*/
static void ac101_deinit(void) {
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
}
/****************************************************************************************
* Don't know when this one is supposed to be called
*/
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
uint16_t regval=0;
regval = i2c_read_reg(I2S1LCK_CTRL);
regval &= 0xe03f;
regval |= (cfg->bclk_div << 9);
regval |= (cfg->lclk_div << 6);
i2c_write_reg(I2S1LCK_CTRL, regval);
}
#endif
/****************************************************************************************
*
*/
@@ -362,21 +383,3 @@ static void ac101_stop(void) {
i2c_write_reg(PLL_CTRL2, value);
}
/****************************************************************************************
*
*/
static void ac101_deinit(void) {
i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
}
/****************************************************************************************
* Don't know when this one is supposed to be called
*/
static void ac101_i2s_config_clock(ac_i2s_clock_t *cfg) {
uint16_t regval=0;
regval = i2c_read_reg(I2S1LCK_CTRL);
regval &= 0xe03f;
regval |= (cfg->bclk_div << 9);
regval |= (cfg->lclk_div << 6);
i2c_write_reg(I2S1LCK_CTRL, regval);
}

View File

@@ -21,7 +21,9 @@
// fifo bufffers
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "squeezelite.h"

View File

@@ -585,7 +585,7 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
int scale = 8 - GDS_GetDepth(display);
// use "fast" version as we are not beyond screen boundaries
if (visu.rotate) {
if (rotate) {
for (int r = 0; r < width; r++) {
for (int c = VU_HEIGHT; --c >= 0;) {
GDS_DrawPixelFast(display, c + x, r + y, *data++ >> scale);
@@ -594,13 +594,13 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
} else {
for (int r = 0; r < width; r++) {
for (int c = 0; c < VU_HEIGHT; c++) {
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale);
}
}
}
} else {
// use "fast" version as we are not beyond screen boundaries
if (visu.rotate) {
if (rotate) {
for (int r = 0; r < width; r++) {
for (int c = VU_HEIGHT; --c >= 0;) {
GDS_DrawPixelFast(display, c + x, r + y, grayMap[*data++]);
@@ -609,11 +609,10 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
} else {
for (int r = 0; r < width; r++) {
for (int c = 0; c < VU_HEIGHT; c++) {
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]);
}
}
}
}
// need to manually set dirty flag as DrawPixel does not do it
@@ -966,9 +965,13 @@ static void visu_update(void) {
}
}
} else if (displayer.width / 2 > 3 * VU_WIDTH / 4) {
int width = visu.rotate ? visu.height : visu.width;
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, width / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, width / 2, visu.row, width / 2, visu.rotate);
if (visu.rotate) {
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.height / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, 0, visu.row + visu.height / 2, visu.height / 2, visu.rotate);
} else {
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.width / 2, visu.rotate);
draw_VU(display, vu_bitmap, visu.bars[1].current, visu.width / 2, visu.row, visu.width / 2, visu.rotate);
}
} else {
int level = (visu.bars[0].current + visu.bars[1].current) / 2;
draw_VU(display, vu_bitmap, level, 0, visu.row, visu.rotate ? visu.height : visu.width, visu.rotate);

View File

@@ -14,6 +14,7 @@
#include "esp_system.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "monitor.h"
mutex_type slimp_mutex;
@@ -60,3 +61,11 @@ u16_t get_RSSI(void) {
if (wifidata.primary != 0) return 100 + wifidata.rssi + 30;
else return 0xffff;
}
u16_t get_plugged(void) {
return jack_inserted_svc() ? PLUG_HEADPHONE : 0;
}
u8_t get_battery(void) {
return (battery_level_svc() * 16) / 100;
}

View File

@@ -63,8 +63,13 @@ extern mutex_type slimp_mutex;
#define LOCK_P mutex_lock(slimp_mutex)
#define UNLOCK_P mutex_unlock(slimp_mutex)
// must provide or define as 0xffff
u16_t get_RSSI(void);
// bitmap of plugs status
#define PLUG_LINE_IN 0x01
#define PLUG_LINE_OUT 0x02
#define PLUG_HEADPHONE 0x04
u16_t get_RSSI(void); // must provide or define as 0xffff
u16_t get_plugged(void); // must provide or define as 0x0
u8_t get_battery(void); // must provide 0..15 or define as 0x0
// to be defined to nothing if you don't want to support these
extern struct visu_export_s {

View File

@@ -31,6 +31,7 @@ sure that using rate_delay would fix that
*/
#include "squeezelite.h"
#include "slimproto.h"
#include "esp_pthread.h"
#include "driver/i2s.h"
#include "driver/i2c.h"
@@ -82,6 +83,7 @@ const struct adac_s *adac = &dac_external;
static log_level loglevel;
static bool (*slimp_handler_chain)(u8_t *data, int len);
static bool jack_mutes_amp;
static bool running, isI2SStarted;
static i2s_config_t i2s_config;
@@ -102,12 +104,42 @@ DECLARE_ALL_MIN_MAX;
static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
static void *output_thread_i2s(void *arg);
static void *output_thread_i2s_stats(void *arg);
static void output_thread_i2s_stats(void *arg);
static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *count);
static void (*jack_handler_chain)(bool inserted);
#define I2C_PORT 0
/****************************************************************************************
* AUDO packet handler
*/
static bool handler(u8_t *data, int len){
bool res = true;
if (!strncmp((char*) data, "audo", 4)) {
struct audo_packet *pkt = (struct audo_packet*) data;
// 0 = headphone (internal speakers off), 1 = sub out,
// 2 = always on (internal speakers on), 3 = always off
if (jack_mutes_amp != (pkt->config == 0)) {
jack_mutes_amp = pkt->config == 0;
config_set_value(NVS_TYPE_STR, "jack_mutes_amp", jack_mutes_amp ? "y" : "n");
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
else adac->speaker(true);
}
LOG_INFO("got AUDO %02x", pkt->config);
} else {
res = false;
}
// chain protocol handlers (bitwise or is fine)
if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
return res;
}
/****************************************************************************************
* jack insertion handler
*/
@@ -164,6 +196,10 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
int silent_do = -1;
char *p;
esp_err_t res;
// chain SLIMP handlers
slimp_handler_chain = slimp_handler;
slimp_handler = handler;
p = config_alloc_get_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
@@ -192,7 +228,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
char *dac_config = config_alloc_get_str("dac_config", CONFIG_DAC_CONFIG, "model=i2s,bck=" STR(CONFIG_I2S_BCK_IO)
",ws=" STR(CONFIG_I2S_WS_IO) ",do=" STR(CONFIG_I2S_DO_IO)
",sda=" STR(CONFIG_I2C_SDA) ",scl=" STR(CONFIG_I2C_SCL)
",mute" STR(CONFIG_MUTE_GPIO));
",mute=" STR(CONFIG_MUTE_GPIO));
i2s_pin_config_t i2s_dac_pin, i2s_spdif_pin;
set_i2s_pin(spdif_config, &i2s_spdif_pin);
@@ -248,7 +284,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
char model[32] = "i2s";
if ((p = strcasestr(dac_config, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", model);
if ((p = strcasestr(dac_config, "mute")) != NULL) {
char mute[8];
char mute[8] = "";
sscanf(p, "%*[^=]=%7[^,]", mute);
mute_control.gpio = atoi(mute);
if ((p = strchr(mute, ':')) != NULL) mute_control.active = atoi(p + 1);
@@ -256,7 +292,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
for (int i = 0; adac == &dac_external && dac_set[i]; i++) if (strcasestr(dac_set[i]->model, model)) adac = dac_set[i];
res = adac->init(dac_config, I2C_PORT, &i2s_config) ? ESP_OK : ESP_FAIL;
res |= i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
res |= i2s_set_pin(CONFIG_I2S_NUM, &i2s_dac_pin);
@@ -502,13 +538,13 @@ static void *output_thread_i2s(void *arg) {
// this does not work well as set_sample_rates resets the fifos (and it's too early)
if (i2s_config.sample_rate != output.current_sample_rate) {
LOG_INFO("changing sampling rate %u to %u", i2s_config.sample_rate, output.current_sample_rate);
/*
if (synced)
if (synced) {
/*
// can sleep for a buffer_queue - 1 and then eat a buffer (discard) if we are synced
usleep(((DMA_BUF_COUNT - 1) * DMA_BUF_LEN * BYTES_PER_FRAME * 1000) / 44100 * 1000);
discard = DMA_BUF_COUNT * DMA_BUF_LEN * BYTES_PER_FRAME;
*/
}
*/
i2s_config.sample_rate = output.current_sample_rate;
i2s_set_sample_rates(CONFIG_I2S_NUM, spdif ? i2s_config.sample_rate * 2 : i2s_config.sample_rate);
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
@@ -552,7 +588,7 @@ static void *output_thread_i2s(void *arg) {
/****************************************************************************************
* Stats output thread
*/
static void *output_thread_i2s_stats(void *arg) {
static void output_thread_i2s_stats(void *arg) {
while (1) {
// no need to lock
output_state state = output.state;
@@ -579,7 +615,6 @@ static void *output_thread_i2s_stats(void *arg) {
}
vTaskDelay( pdMS_TO_TICKS( STATS_PERIOD_MS ) );
}
return NULL;
}
/****************************************************************************************

View File

@@ -190,6 +190,7 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
packN(&pkt.bytes_received_L, (u64_t)status.stream_bytes & 0xffffffff);
#if EMBEDDED
packn(&pkt.signal_strength, get_RSSI());
packn(&pkt.voltage, (get_battery() << 4) | get_plugged());
#else
pkt.signal_strength = 0xffff;
#endif
@@ -197,7 +198,6 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
packN(&pkt.output_buffer_size, status.output_size);
packN(&pkt.output_buffer_fullness, status.output_full);
packN(&pkt.elapsed_seconds, ms_played / 1000);
// voltage;
packN(&pkt.elapsed_milliseconds, ms_played);
pkt.server_timestamp = server_timestamp; // keep this is server format - don't unpack/pack
// error_code;

View File

@@ -178,6 +178,12 @@ struct codc_packet {
u8_t pcm_endianness;
};
// initially Boom
struct audo_packet {
char opcode[4];
u8_t config;
};
#ifndef SUN
#pragma pack(pop)
#else

View File

@@ -21,7 +21,9 @@
// stream thread
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "squeezelite.h"

View File

@@ -97,17 +97,17 @@ menu "Squeezelite-ESP32"
I2S dma channel to use.
config I2S_BCK_IO
int "I2S Bit clock GPIO number. "
default 33
default -1
help
I2S Bit Clock gpio pin to use.
config I2S_WS_IO
int "I2S Word Select GPIO number. "
default 25
default -1
help
I2S Word Select gpio pin to use.
config I2S_DO_IO
int "I2S Data Output GPIO number. "
default 32
default -1
help
I2S data output gpio pin to use.
config I2S_DI_IO

View File

@@ -37,7 +37,7 @@ extern bool bypass_wifi_manager;
extern EventGroupHandle_t wifi_event_group;
extern const int CONNECTED_BIT;
static const char * TAG = "cmd_wifi";
//static const char * TAG = "cmd_wifi";
/** Arguments used by 'join' function */
static struct {
struct arg_int *timeout;
@@ -123,9 +123,8 @@ static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
return (bits & CONNECTED_BIT) != 0;
}
static int set_auto_connect(int argc, char **argv)
{
//static int set_auto_connect(int argc, char **argv)
//{
// int nerrors = arg_parse(argc, argv, (void **) &join_args);
// if (nerrors != 0) {
// arg_print_errors(stderr, join_args.end, argv[0]);
@@ -147,8 +146,9 @@ static int set_auto_connect(int argc, char **argv)
// return 1;
// }
// ESP_LOGI(__func__, "Connected");
return 0;
}
// return 0;
//}
static int connect(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **) &join_args);

View File

@@ -333,6 +333,9 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "jack_mutes_amp");
config_set_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);

Binary file not shown.

View File

@@ -9,17 +9,95 @@ use List::Util qw(min);
use Slim::Utils::Log;
use Slim::Utils::Prefs;
my $sprefs = preferences('server');
my $prefs = preferences('plugin.squeezeesp32');
my $log = logger('plugin.squeezeesp32');
{
__PACKAGE__->mk_accessor('rw', 'tone_update');
}
sub new {
my $class = shift;
my $client = $class->SUPER::new(@_);
$client->init_accessor(
tone_update => 0,
);
return $client;
}
our $defaultPrefs = {
'analogOutMode' => 0,
'bass' => 0,
'treble' => 0,
'lineInAlwaysOn' => 0,
'lineInLevel' => 50,
'menuItem' => [qw(
NOW_PLAYING
BROWSE_MUSIC
RADIO
PLUGIN_MY_APPS_MODULE_NAME
PLUGIN_APP_GALLERY_MODULE_NAME
FAVORITES
GLOBAL_SEARCH
PLUGIN_LINE_IN
PLUGINS
SETTINGS
SQUEEZENETWORK_CONNECT
)],
};
my $handlersAdded;
sub model { 'squeezeesp32' }
sub modelName { 'SqueezeESP32' }
sub hasIR { 0 }
sub hasScrolling { 1 }
sub hasIR { 1 }
# TODO: add in settings when ready
sub hasLineIn { 0 }
sub hasHeadSubOut { 1 }
sub maxTreble { 20 }
sub minTreble { -13 }
sub maxBass { 20 }
sub minBass { -13 }
sub init {
my $client = shift;
if (!$handlersAdded) {
# Add a handler for line-in/out status changes
Slim::Networking::Slimproto::addHandler( LIOS => \&lineInOutStatus );
# Create a new event for sending LIOS updates
Slim::Control::Request::addDispatch(
['lios', '_state'],
[1, 0, 0, undef],
);
Slim::Control::Request::addDispatch(
['lios', 'linein', '_state'],
[1, 0, 0, undef],
);
Slim::Control::Request::addDispatch(
['lios', 'lineout', '_state'],
[1, 0, 0, undef],
);
$handlersAdded = 1;
}
$client->SUPER::init(@_);
$client->config_artwork();
$client->config_artwork;
}
sub initPrefs {
my $client = shift;
$sprefs->client($client)->init($defaultPrefs);
$client->SUPER::initPrefs;
}
# Allow the player to define it's display width (and probably more)
@@ -50,12 +128,42 @@ sub playerSettingsFrame {
$client->SUPER::playerSettingsFrame($data_ref);
}
sub hasScrolling {
return 1;
sub bass {
my ($client, $new) = @_;
my $value = $client->SUPER::bass($new);
$client->update_equalizer($value, [2, 1, 3]) if defined $new;
return $value;
}
sub hasIR {
return 1;
sub treble {
my ($client, $new) = @_;
my $value = $client->SUPER::treble($new);
$client->update_equalizer($value, [8, 9, 7]) if defined $new;
return $value;
}
sub update_equalizer {
my ($client, $value, $index) = @_;
return if $client->tone_update;
my $equalizer = $prefs->client($client)->get('equalizer');
$equalizer->[$index->[0]] = $value;
$equalizer->[$index->[1]] = int($value / 2 + 0.5);
$equalizer->[$index->[2]] = int($value / 4 + 0.5);
$prefs->client($client)->set('equalizer', $equalizer);
}
sub update_tones {
my ($client, $equalizer) = @_;
$client->tone_update(1);
$sprefs->client($client)->set('bass', int(($equalizer->[1] * 2 + $equalizer->[2] + $equalizer->[3] * 4) / 7 + 0.5));
$sprefs->client($client)->set('treble', int(($equalizer->[7] * 4 + $equalizer->[8] + $equalizer->[9] * 2) / 7 + 0.5));
$client->tone_update(0);
}
sub update_artwork {
@@ -131,4 +239,61 @@ sub reconnect {
$client->SUPER::reconnect(@_);
}
# Change the analog output mode between headphone and sub-woofer
# If no mode is specified, the value of the client's analogOutMode preference is used.
# Otherwise the mode is temporarily changed to the given value without altering the preference.
sub setAnalogOutMode {
my $client = shift;
# 0 = headphone (internal speakers off), 1 = sub out,
# 2 = always on (internal speakers on), 3 = always off
my $mode = shift;
if (! defined $mode) {
$mode = $sprefs->client($client)->get('analogOutMode');
}
my $data = pack('C', $mode);
$client->sendFrame('audo', \$data);
}
# LINE_IN 0x01
# LINE_OUT 0x02
# HEADPHONE 0x04
sub lineInConnected {
my $state = Slim::Networking::Slimproto::voltage(shift) || return 0;
return $state & 0x01 || 0;
}
sub lineOutConnected {
my $state = Slim::Networking::Slimproto::voltage(shift) || return 0;
return $state & 0x02 || 0;
}
sub lineInOutStatus {
my ( $client, $data_ref ) = @_;
my $state = unpack 'n', $$data_ref;
my $oldState = {
in => $client->lineInConnected(),
out => $client->lineOutConnected(),
};
Slim::Networking::Slimproto::voltage( $client, $state );
Slim::Control::Request::notifyFromArray( $client, [ 'lios', $state ] );
if ($oldState->{in} != $client->lineInConnected()) {
Slim::Control::Request::notifyFromArray( $client, [ 'lios', 'linein', $client->lineInConnected() ] );
if ( Slim::Utils::PluginManager->isEnabled('Slim::Plugin::LineIn::Plugin')) {
Slim::Plugin::LineIn::Plugin::lineInItem($client, 1);
}
}
if ($oldState->{out} != $client->lineOutConnected()) {
Slim::Control::Request::notifyFromArray( $client, [ 'lios', 'lineout', $client->lineOutConnected() ] );
}
}
1;

View File

@@ -73,6 +73,7 @@ sub handler {
$equalizer->[$i] = $paramRef->{"pref_equalizer.$i"} || 0;
}
$cprefs->set('equalizer', $equalizer);
$client->update_tones($equalizer);
}
if ($client->displayWidth) {

View File

@@ -10,6 +10,6 @@
<name>PLUGIN_SQUEEZEESP32</name>
<description>PLUGIN_SQUEEZEESP32_DESC</description>
<module>Plugins::SqueezeESP32::Plugin</module>
<version>0.94</version>
<version>0.103</version>
<creator>Philippe</creator>
</extensions>

View File

@@ -1,10 +1,10 @@
<?xml version='1.0' standalone='yes'?>
<extensions>
<plugins>
<plugin version="0.94" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<plugin version="0.103" name="SqueezeESP32" minTarget="7.9" maxTarget="*">
<link>https://github.com/sle118/squeezelite-esp32</link>
<creator>Philippe</creator>
<sha>a9bf10b47d13508ba051e4067cdabc2c283f4824</sha>
<sha>d07bb3b0a283fbde50e5533dca695a4505971f03</sha>
<email>philippe_44@outlook.com</email>
<desc lang="EN">SqueezeESP32 additional player id (100)</desc>
<url>http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip</url>