mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 12:07:09 +03:00
Merge remote-tracking branch 'origin/master' into httpd
Conflicts: components/wifi-manager/http_server.c
This commit is contained in:
@@ -29,7 +29,7 @@ extern "C" {
|
||||
|
||||
|
||||
static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
|
||||
static const char * TAG = "platform_esp32";
|
||||
static const char * TAG = "cmd_nvs";
|
||||
|
||||
static struct {
|
||||
struct arg_str *key;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char * TAG = "platform_esp32";
|
||||
static const char * TAG = "btappcore";
|
||||
|
||||
static void bt_app_task_handler(void *arg);
|
||||
static bool bt_app_send_msg(bt_app_msg_t *msg);
|
||||
|
||||
@@ -162,7 +162,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
|
||||
va_end(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// now handle events for display
|
||||
switch(cmd) {
|
||||
case BT_SINK_AUDIO_STARTED:
|
||||
@@ -175,7 +175,7 @@ static bool cmd_handler(bt_sink_cmd_t cmd, ...) {
|
||||
displayer_control(DISPLAYER_TIMER_RUN);
|
||||
break;
|
||||
case BT_SINK_STOP:
|
||||
// not sure of difference between pause and stop for displayer
|
||||
// not sure of difference between pause and stop for displayer
|
||||
case BT_SINK_PAUSE:
|
||||
displayer_control(DISPLAYER_TIMER_PAUSE);
|
||||
break;
|
||||
@@ -397,7 +397,7 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
|
||||
bt_av_playback_changed();
|
||||
break;
|
||||
case ESP_AVRC_RN_PLAY_POS_CHANGED:
|
||||
ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos);
|
||||
ESP_LOGD(BT_AV_TAG, "Play position changed: %d (ms)", event_parameter->play_pos);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, event_parameter->play_pos, -1);
|
||||
bt_av_play_pos_changed();
|
||||
break;
|
||||
@@ -440,7 +440,7 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
|
||||
break;
|
||||
}
|
||||
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: {
|
||||
ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
|
||||
ESP_LOGD(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id);
|
||||
bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
|
||||
if ((p = strcasestr(buf, "timing_port")) != NULL) sscanf(p, "%*[^=]=%hu", &tport);
|
||||
if ((p = strcasestr(buf, "control_port")) != NULL) sscanf(p, "%*[^=]=%hu", &cport);
|
||||
|
||||
|
||||
rtp = rtp_init(ctx->peer, ctx->latency, ctx->rtsp.aeskey, ctx->rtsp.aesiv,
|
||||
ctx->rtsp.fmtp, cport, tport, ctx->cmd_cb, ctx->data_cb);
|
||||
|
||||
@@ -630,9 +630,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
|
||||
float volume;
|
||||
|
||||
sscanf(p, "%*[^:]:%f", &volume);
|
||||
LOG_INFO("[%p]: SET PARAMETER volume %f", ctx, volume);
|
||||
volume = (volume == -144.0) ? 0 : (1 + volume / 30);
|
||||
volume = (volume == -144.0) ? 0 : (1 + volume / 30);
|
||||
success = ctx->cmd_cb(RAOP_VOLUME, volume);
|
||||
success = ctx->cmd_cb(RAOP_VOLUME, volume);
|
||||
} else if (body && (p = strcasestr(body, "progress")) != NULL) {
|
||||
int start, current, stop = 0;
|
||||
|
||||
// we want ms, not s
|
||||
|
||||
@@ -564,7 +564,7 @@ static void *rtp_thread_func(void *arg) {
|
||||
bool ntp_sent;
|
||||
char *packet = malloc(MAX_PACKET);
|
||||
rtp_t *ctx = (rtp_t*) arg;
|
||||
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (ctx->rtp_sockets[i].sock > sock) sock = ctx->rtp_sockets[i].sock;
|
||||
// send synchro request 3 times
|
||||
@@ -638,7 +638,9 @@ static void *rtp_thread_func(void *arg) {
|
||||
u32_t rtp_now_latency = ntohl(*(u32_t*)(pktp+4));
|
||||
u64_t remote = (((u64_t) ntohl(*(u32_t*)(pktp+8))) << 32) + ntohl(*(u32_t*)(pktp+12));
|
||||
u32_t rtp_now = ntohl(*(u32_t*)(pktp+16));
|
||||
u16_t flags = ntohs(*(u16_t*)(pktp+2));
|
||||
u32_t remote_gap = NTP2MS(remote - ctx->timing.remote);
|
||||
|
||||
// something is wrong and if we are supposed to be NTP synced, better ask for re-sync
|
||||
if (remote_gap > 10000) {
|
||||
if (ctx->synchro.status & NTP_SYNC) rtp_request_timing(ctx);
|
||||
@@ -683,8 +685,9 @@ static void *rtp_thread_func(void *arg) {
|
||||
case 0x53: {
|
||||
u64_t expected;
|
||||
u32_t reference = ntohl(*(u32_t*)(pktp+12)); // only low 32 bits in our case
|
||||
u64_t remote =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
|
||||
u64_t remote =(((u64_t) ntohl(*(u32_t*)(pktp+16))) << 32) + ntohl(*(u32_t*)(pktp+20));
|
||||
u32_t roundtrip = gettime_ms() - reference;
|
||||
|
||||
// better discard sync packets when roundtrip is suspicious and ask for another one
|
||||
if (roundtrip > 100) {
|
||||
rtp_request_timing(ctx);
|
||||
|
||||
@@ -58,3 +58,22 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
|
||||
if(i2c_port) *i2c_port=i2c_system_port;
|
||||
return &i2c;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void parse_set_GPIO(void (*cb)(int gpio, char *value)) {
|
||||
char *nvs_item, *p, type[4];
|
||||
int gpio;
|
||||
|
||||
if ((nvs_item = config_alloc_get(NVS_TYPE_STR, "set_GPIO")) == NULL) return;
|
||||
|
||||
p = nvs_item;
|
||||
|
||||
do {
|
||||
if (sscanf(p, "%d=%3[^,]", &gpio, type) > 0) cb(gpio, type);
|
||||
p = strchr(p, ',');
|
||||
} while (p++);
|
||||
|
||||
free(nvs_item);
|
||||
}
|
||||
@@ -13,3 +13,4 @@
|
||||
|
||||
esp_err_t config_i2c_set(const i2c_config_t * config, int port);
|
||||
const i2c_config_t * config_i2c_get(int * i2c_port);
|
||||
void parse_set_GPIO(void (*cb)(int gpio, char *value));
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
#include "driver/adc.h"
|
||||
#include "battery.h"
|
||||
|
||||
/*
|
||||
There is a bug in esp32 which causes a spurious interrupt on gpio 36/39 when
|
||||
using ADC, AMP and HALL sensor. Rather than making battery aware, we just ignore
|
||||
if as the interrupt lasts 80ns and should be debounced (and the ADC read does not
|
||||
happen very often)
|
||||
*/
|
||||
|
||||
#define BATTERY_TIMER (10*1000)
|
||||
|
||||
static const char *TAG = "battery";
|
||||
@@ -27,6 +34,13 @@ static struct {
|
||||
TimerHandle_t timer;
|
||||
} battery;
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
int battery_value_svc(void) {
|
||||
return battery.avg;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
#include "esp_task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "buttons.h"
|
||||
#include "globdefs.h"
|
||||
|
||||
bool gpio36_39_used;
|
||||
|
||||
static const char * TAG = "buttons";
|
||||
|
||||
@@ -208,7 +211,10 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
|
||||
if (gpio == 36 || gpio == 39) gpio36_39_used = true;
|
||||
|
||||
gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
|
||||
gpio_intr_enable(gpio);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
// here we should include all possible drivers
|
||||
extern struct display_s SSD1306_display;
|
||||
|
||||
struct display_s *display;
|
||||
struct display_s *display = NULL;
|
||||
|
||||
static const char *TAG = "display";
|
||||
|
||||
@@ -72,6 +72,7 @@ void display_init(char *welcome) {
|
||||
init = true;
|
||||
ESP_LOGI(TAG, "Display initialization successful");
|
||||
} else {
|
||||
display = NULL;
|
||||
ESP_LOGE(TAG, "Display initialization failed");
|
||||
}
|
||||
} else {
|
||||
@@ -103,7 +104,7 @@ void display_init(char *welcome) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* This is not really thread-safe as displayer_task might be in the middle of line drawing
|
||||
* 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
|
||||
* feature which is secondary (the LMS version of scrolling is thread-safe)
|
||||
*/
|
||||
@@ -128,11 +129,19 @@ static void displayer_task(void *args) {
|
||||
if (scroll_sleep <= 10) {
|
||||
// something to scroll (or we'll wake-up every pause ms ... no big deal)
|
||||
if (*displayer.string && displayer.state == DISPLAYER_ACTIVE) {
|
||||
display->line(2, -displayer.offset, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string);
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
// need to work with local copies as we don't want to suspend caller
|
||||
int offset = -displayer.offset;
|
||||
char *string = strdup(displayer.string);
|
||||
scroll_sleep = displayer.offset ? displayer.speed : displayer.pause;
|
||||
displayer.offset = displayer.offset >= displayer.boundary ? 0 : (displayer.offset + min(displayer.by, displayer.boundary - displayer.offset));
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
// now display using safe copies, can be lengthy
|
||||
display->line(2, offset, DISPLAY_CLEAR | DISPLAY_UPDATE, string);
|
||||
free(string);
|
||||
} else {
|
||||
scroll_sleep = DEFAULT_SLEEP;
|
||||
}
|
||||
@@ -149,8 +158,8 @@ static void displayer_task(void *args) {
|
||||
displayer.tick = tick;
|
||||
displayer.elapsed += elapsed / 1000;
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
if (displayer.elapsed < 3600) sprintf(counter, "%2u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
|
||||
else sprintf(counter, "%2u:%2u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
|
||||
if (displayer.elapsed < 3600) sprintf(counter, "%5u:%02u", displayer.elapsed / 60, displayer.elapsed % 60);
|
||||
else sprintf(counter, "%2u:%02u:%02u", displayer.elapsed / 3600, (displayer.elapsed % 3600) / 60, displayer.elapsed % 60);
|
||||
display->line(1, DISPLAY_RIGHT, (DISPLAY_CLEAR | DISPLAY_ONLY_EOL) | DISPLAY_UPDATE, counter);
|
||||
timer_sleep = 1000;
|
||||
} else timer_sleep = max(1000 - elapsed, 0);
|
||||
@@ -171,6 +180,10 @@ void displayer_metadata(char *artist, char *album, char *title) {
|
||||
char *string = displayer.string, *p;
|
||||
int len = SCROLLABLE_SIZE;
|
||||
|
||||
// need a display!
|
||||
if (!display) return;
|
||||
|
||||
// just do title if there is no config set
|
||||
if (!displayer.metadata_config) {
|
||||
strncpy(displayer.string, title ? title : "", SCROLLABLE_SIZE);
|
||||
return;
|
||||
@@ -218,7 +231,7 @@ void displayer_metadata(char *artist, char *album, char *title) {
|
||||
p = strchr(q + 1, '%');
|
||||
}
|
||||
} else {
|
||||
string = strdup(title ? title : "");
|
||||
strncpy(string, title ? title : "", SCROLLABLE_SIZE);
|
||||
}
|
||||
|
||||
// get optional scroll speed
|
||||
@@ -232,11 +245,13 @@ void displayer_metadata(char *artist, char *album, char *title) {
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void displayer_scroll(char *string, int speed) {
|
||||
// need a display!
|
||||
if (!display) return;
|
||||
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
if (speed) displayer.speed = speed;
|
||||
@@ -252,10 +267,13 @@ void displayer_scroll(char *string, int speed) {
|
||||
*
|
||||
*/
|
||||
void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
|
||||
// need a display!
|
||||
if (!display) return;
|
||||
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
if (elapsed >= 0) displayer.elapsed = elapsed;
|
||||
if (duration >= 0) displayer.duration = duration;
|
||||
if (elapsed >= 0) displayer.elapsed = elapsed / 1000;
|
||||
if (duration >= 0) displayer.duration = duration / 1000;
|
||||
if (displayer.timer) displayer.tick = xTaskGetTickCount();
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
@@ -267,6 +285,8 @@ void displayer_timer(enum displayer_time_e mode, int elapsed, int duration) {
|
||||
void displayer_control(enum displayer_cmd_e cmd, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!display) return;
|
||||
|
||||
va_start(args, cmd);
|
||||
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ static bool set_font(int num, enum display_font_e font, int space) {
|
||||
lines[0].y = lines[0].space;
|
||||
for (int i = 1; i <= num; i++) lines[i].y = lines[i-1].y + lines[i-1].font->Height + lines[i].space;
|
||||
|
||||
ESP_LOGI(TAG, "adding line %u at %u (height:%u)", num + 1, lines[num].y, lines[num].font->Height);
|
||||
ESP_LOGI(TAG, "Adding line %u at %d (height:%u)", num + 1, lines[num].y, lines[num].font->Height);
|
||||
|
||||
if (lines[num].y + lines[num].font->Height > Display.Height) {
|
||||
ESP_LOGW(TAG, "line does not fit display");
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#define I2C_SYSTEM_PORT 1
|
||||
extern int i2c_system_port;
|
||||
extern bool gpio36_39_used;
|
||||
|
||||
#ifdef CONFIG_SQUEEZEAMP
|
||||
#define JACK_GPIO 34
|
||||
@@ -31,4 +32,5 @@ extern int i2c_system_port;
|
||||
#else
|
||||
#define LED_GREEN_GPIO CONFIG_LED_GREEN_GPIO
|
||||
#define LED_RED_GPIO CONFIG_LED_RED_GPIO
|
||||
#define JACK_GPIO CONFIG_JACK_GPIO
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "buttons.h"
|
||||
#include "led.h"
|
||||
#include "globdefs.h"
|
||||
#include "config.h"
|
||||
|
||||
#define MONITOR_TIMER (10*1000)
|
||||
|
||||
@@ -83,19 +84,31 @@ bool spkfault_svc (void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "driver/rtc_io.h"
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_jack_gpio(int gpio, char *value) {
|
||||
if (!strcasecmp(value, "jack")) {
|
||||
ESP_LOGI(TAG,"Adding jack detection GPIO %d", gpio);
|
||||
|
||||
gpio_pad_select_gpio(JACK_GPIO);
|
||||
gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
|
||||
|
||||
// re-use button management for jack handler, it's a GPIO after all
|
||||
button_create(NULL, JACK_GPIO, BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void monitor_svc_init(void) {
|
||||
ESP_LOGI(TAG, "Initializing monitoring");
|
||||
|
||||
#ifdef JACK_GPIO
|
||||
gpio_pad_select_gpio(JACK_GPIO);
|
||||
gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
|
||||
|
||||
// re-use button management for jack handler, it's a GPIO after all
|
||||
button_create(NULL, JACK_GPIO, BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
|
||||
|
||||
#if !defined(JACK_GPIO) || JACK_GPIO == -1
|
||||
parse_set_GPIO(set_jack_gpio);
|
||||
#else
|
||||
set_jack_gpio(JACK_GPIO, "jack");
|
||||
#endif
|
||||
|
||||
#ifdef SPKFAULT_GPIO
|
||||
@@ -107,6 +120,12 @@ void monitor_svc_init(void) {
|
||||
button_create(NULL, SPKFAULT_GPIO, BUTTON_LOW, true, 0, spkfault_handler_default, 0, -1);
|
||||
#endif
|
||||
|
||||
monitor_timer = xTimerCreate("monitor", MONITOR_TIMER / portTICK_RATE_MS, pdTRUE, NULL, monitor_callback);
|
||||
xTimerStart(monitor_timer, portMAX_DELAY);
|
||||
// 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);
|
||||
}
|
||||
free(p);
|
||||
|
||||
}
|
||||
|
||||
@@ -26,3 +26,5 @@ extern bool jack_inserted_svc(void);
|
||||
extern void (*spkfault_handler_svc)(bool inserted);
|
||||
extern bool spkfault_svc(void);
|
||||
|
||||
extern int battery_value_svc(void);
|
||||
|
||||
|
||||
@@ -25,6 +25,25 @@ int i2c_system_port = I2C_SYSTEM_PORT;
|
||||
|
||||
static const char *TAG = "services";
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_power_gpio(int gpio, char *value) {
|
||||
bool parsed = true;
|
||||
|
||||
if (!strcasecmp(value, "vcc") ) {
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(gpio, 1);
|
||||
} else if (!strcasecmp(value, "gnd")) {
|
||||
gpio_pad_select_gpio(gpio);
|
||||
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);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -40,20 +59,8 @@ void services_init(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// set fixed gpio if any
|
||||
if ((nvs_item = config_alloc_get(NVS_TYPE_STR, "Vcc_GPIO")) != NULL) {
|
||||
char *p = nvs_item;
|
||||
while (p && *p) {
|
||||
int gpio = atoi(p);
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(gpio, 1);
|
||||
p = strchr(p, ',');
|
||||
ESP_LOGI(TAG, "set GPIO %u to Vcc", gpio);
|
||||
if (p) p++;
|
||||
}
|
||||
free(nvs_item);
|
||||
}
|
||||
// set potential power GPIO
|
||||
parse_set_GPIO(set_power_gpio);
|
||||
|
||||
const i2c_config_t * i2c_config = config_i2c_get(&i2c_system_port);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ void SSD1306_FontDrawChar( struct SSD1306_Device* DisplayHandle, char Character,
|
||||
|
||||
NullCheck( ( GlyphData = GetCharPtr( DisplayHandle->Font, Character ) ), return );
|
||||
|
||||
if ( Character >= DisplayHandle->Font->StartChar || Character <= DisplayHandle->Font->EndChar ) {
|
||||
if ( Character >= DisplayHandle->Font->StartChar && Character <= DisplayHandle->Font->EndChar ) {
|
||||
/* The first byte in the glyph data is the width of the character in pixels, skip over */
|
||||
GlyphData++;
|
||||
GlyphColumnLen = RoundUpFontHeight( DisplayHandle->Font ) / 8;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "esp32/rom/uart.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char * TAG = "platform_esp32";
|
||||
static const char * TAG = "ota";
|
||||
extern esp_err_t start_ota(const char * bin_url);
|
||||
static struct {
|
||||
struct arg_str *url;
|
||||
|
||||
435
components/squeezelite/a1s/ac101.c
Normal file
435
components/squeezelite/a1s/ac101.c
Normal file
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* ESPRESSIF MIT License
|
||||
*
|
||||
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
||||
*
|
||||
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
|
||||
* it is free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_types.h>
|
||||
#include <esp_system.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <driver/i2c.h>
|
||||
#include "adac.h"
|
||||
|
||||
//#include "audio_hal.h"
|
||||
#include "ac101.h"
|
||||
|
||||
const static char TAG[] = "AC101";
|
||||
|
||||
#define AC_ASSERT(a, format, b, ...) \
|
||||
if ((a) != 0) { \
|
||||
ESP_LOGE(TAG, format, ##__VA_ARGS__); \
|
||||
return b;\
|
||||
}
|
||||
|
||||
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
|
||||
static void deinit(void);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static void volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
struct adac_s dac_a1s = { init, deinit, power, speaker, headset, volume };
|
||||
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val);
|
||||
static uint16_t i2c_read_reg(uint8_t reg);
|
||||
static esp_err_t ac101_start(ac_module_t mode);
|
||||
static esp_err_t ac101_stop(void);
|
||||
static esp_err_t ac101_set_earph_volume(uint8_t volume);
|
||||
static esp_err_t ac101_set_spk_volume(uint8_t volume);
|
||||
|
||||
//static void pa_power(bool enable);
|
||||
|
||||
static int i2c_port;
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
|
||||
esp_err_t res;
|
||||
|
||||
ESP_LOGI(TAG, "Initializing AC101");
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = 33,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = 32,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 100000,
|
||||
};
|
||||
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
ESP_LOGI(TAG, "DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
res = i2c_write_reg(CHIP_AUDIO_RS, 0x123);
|
||||
|
||||
//huh?
|
||||
//vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
if (ESP_OK != res) {
|
||||
ESP_LOGE(TAG, "reset failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
i2c_write_reg(SPKOUT_CTRL, 0xe880);
|
||||
|
||||
// Enable the PLL from 256*44.1KHz MCLK source
|
||||
i2c_write_reg(PLL_CTRL1, 0x014f);
|
||||
//res |= i2c_write_reg(PLL_CTRL2, 0x83c0);
|
||||
i2c_write_reg(PLL_CTRL2, 0x8600);
|
||||
|
||||
//Clocking system
|
||||
i2c_write_reg(SYSCLK_CTRL, 0x8b08);
|
||||
i2c_write_reg(MOD_CLK_ENA, 0x800c);
|
||||
i2c_write_reg(MOD_RST_CTRL, 0x800c);
|
||||
i2c_write_reg(I2S_SR_CTRL, 0x7000); //sample rate
|
||||
|
||||
//AIF config
|
||||
i2c_write_reg(I2S1LCK_CTRL, 0x8850); //BCLK/LRCK
|
||||
i2c_write_reg(I2S1_SDOUT_CTRL, 0xc000); //
|
||||
i2c_write_reg(I2S1_SDIN_CTRL, 0xc000);
|
||||
i2c_write_reg(I2S1_MXR_SRC, 0x2200); //
|
||||
|
||||
i2c_write_reg(ADC_SRCBST_CTRL, 0xccc4);
|
||||
i2c_write_reg(ADC_SRC, 0x2020);
|
||||
i2c_write_reg(ADC_DIG_CTRL, 0x8000);
|
||||
i2c_write_reg(ADC_APC_CTRL, 0xbbc3);
|
||||
|
||||
//Path Configuration
|
||||
i2c_write_reg(DAC_MXR_SRC, 0xcc00);
|
||||
i2c_write_reg(DAC_DIG_CTRL, 0x8000);
|
||||
i2c_write_reg(OMIXER_SR, 0x0081);
|
||||
i2c_write_reg(OMIXER_DACA_CTRL, 0xf080);//}
|
||||
|
||||
//* Enable Speaker output
|
||||
i2c_write_reg(0x58, 0xeabd);
|
||||
|
||||
//ac101_pa_power(true);
|
||||
|
||||
uint16_t regval;
|
||||
|
||||
// configure I2S
|
||||
regval = i2c_read_reg(I2S1LCK_CTRL);
|
||||
regval &= 0xffc3;
|
||||
regval |= (AC_MODE_SLAVE << 15);
|
||||
regval |= (BIT_LENGTH_16_BITS << 4);
|
||||
regval |= (AC_MODE_SLAVE << 2);
|
||||
res |= i2c_write_reg(I2S1LCK_CTRL, regval);
|
||||
res |= i2c_write_reg(I2S_SR_CTRL, SAMPLE_RATE_44100);
|
||||
|
||||
// configure I2S pins & install driver
|
||||
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = 27, .ws_io_num = 26,
|
||||
.data_out_num = 25, .data_in_num = 35 //Not used
|
||||
};
|
||||
i2s_driver_install(i2s_num, i2s_config, 0, NULL);
|
||||
i2s_set_pin(i2s_num, &i2s_pin_config);
|
||||
|
||||
ESP_LOGI(TAG, "DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static void deinit(void) {
|
||||
i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
static void volume(unsigned left, unsigned right) {
|
||||
// nothing at that point, volume is handled by backend
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* power
|
||||
*/
|
||||
static void power(adac_power_e mode) {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
switch(mode) {
|
||||
case ADAC_STANDBY:
|
||||
case ADAC_OFF:
|
||||
ret = ac101_stop();
|
||||
break;
|
||||
case ADAC_ON:
|
||||
ret = ac101_start(AC_MODULE_DAC);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "unknown power command");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != ESP_OK) ESP_LOGW(TAG, "can't start AC101 %d", ret);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
if (active) i2c_write_reg(SPKOUT_CTRL, 0xeabd);
|
||||
else i2c_write_reg(SPKOUT_CTRL, 0xe880); //disable speaker
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* headset
|
||||
*/
|
||||
static void headset(bool active) {
|
||||
if (active) {
|
||||
i2c_write_reg(OMIXER_DACA_CTRL, 0xff80);
|
||||
i2c_write_reg(HPOUT_CTRL, 0xc3c1);
|
||||
i2c_write_reg(HPOUT_CTRL, 0xcb00);
|
||||
// huh?
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
i2c_write_reg(HPOUT_CTRL, 0xfbc0);
|
||||
} else {
|
||||
i2c_write_reg(HPOUT_CTRL, 0x01); //disable earphone
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t i2c_write_reg(uint8_t reg, uint16_t val)
|
||||
{
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
esp_err_t ret =0;
|
||||
uint8_t send_buff[4];
|
||||
send_buff[0] = (AC101_ADDR << 1);
|
||||
send_buff[1] = reg;
|
||||
send_buff[2] = (val>>8) & 0xff;
|
||||
send_buff[3] = val & 0xff;
|
||||
ret |= i2c_master_start(cmd);
|
||||
ret |= i2c_master_write(cmd, send_buff, 4, ACK_CHECK_EN);
|
||||
ret |= i2c_master_stop(cmd);
|
||||
ret |= i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static uint16_t i2c_read_reg(uint8_t reg) {
|
||||
uint8_t data[2] = { 0 };
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | WRITE_BIT, ACK_CHECK_EN);
|
||||
i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, ( AC101_ADDR << 1 ) | READ_BIT, ACK_CHECK_EN); //check or not
|
||||
i2c_master_read(cmd, data, 2, ACK_VAL);
|
||||
i2c_master_stop(cmd);
|
||||
i2c_master_cmd_begin(i2c_port, cmd, 1000 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
return (data[0] << 8) + data[1];;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_codec_clk(ac_adda_fs_i2s1_t rate) {
|
||||
i2c_write_reg(I2S_SR_CTRL, rate);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static int ac101_get_spk_volume(void) {
|
||||
int res;
|
||||
res = i2c_read_reg(SPKOUT_CTRL);
|
||||
res &= 0x1f;
|
||||
return res*2;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t ac101_set_spk_volume(uint8_t volume) {
|
||||
uint16_t res;
|
||||
esp_err_t ret;
|
||||
volume = volume/2;
|
||||
res = i2c_read_reg(SPKOUT_CTRL);
|
||||
res &= (~0x1f);
|
||||
volume &= 0x1f;
|
||||
res |= volume;
|
||||
ret = i2c_write_reg(SPKOUT_CTRL,res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static int ac101_get_earph_volume(void) {
|
||||
int res;
|
||||
res = i2c_read_reg(HPOUT_CTRL);
|
||||
return (res>>4)&0x3f;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t ac101_set_earph_volume(uint8_t volume) {
|
||||
uint16_t res,tmp;
|
||||
esp_err_t ret;
|
||||
res = i2c_read_reg(HPOUT_CTRL);
|
||||
tmp = ~(0x3f<<4);
|
||||
res &= tmp;
|
||||
volume &= 0x3f;
|
||||
res |= (volume << 4);
|
||||
ret = i2c_write_reg(HPOUT_CTRL,res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t ac101_set_output_mixer_gain(ac_output_mixer_gain_t gain,ac_output_mixer_source_t source)
|
||||
{
|
||||
uint16_t regval,temp,clrbit;
|
||||
esp_err_t ret;
|
||||
regval = i2c_read_reg(OMIXER_BST1_CTRL);
|
||||
switch(source){
|
||||
case SRC_MIC1:
|
||||
temp = (gain&0x7) << 6;
|
||||
clrbit = ~(0x7<<6);
|
||||
break;
|
||||
case SRC_MIC2:
|
||||
temp = (gain&0x7) << 3;
|
||||
clrbit = ~(0x7<<3);
|
||||
break;
|
||||
case SRC_LINEIN:
|
||||
temp = (gain&0x7);
|
||||
clrbit = ~0x7;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
regval &= clrbit;
|
||||
regval |= temp;
|
||||
ret = i2c_write_reg(OMIXER_BST1_CTRL,regval);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t ac101_start(ac_module_t mode) {
|
||||
esp_err_t res = 0;
|
||||
|
||||
if (mode == AC_MODULE_LINE) {
|
||||
res |= i2c_write_reg(0x51, 0x0408);
|
||||
res |= i2c_write_reg(0x40, 0x8000);
|
||||
res |= i2c_write_reg(0x50, 0x3bc0);
|
||||
}
|
||||
if (mode == AC_MODULE_ADC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
|
||||
//I2S1_SDOUT_CTRL
|
||||
//res |= i2c_write_reg(PLL_CTRL2, 0x8120);
|
||||
res |= i2c_write_reg(0x04, 0x800c);
|
||||
res |= i2c_write_reg(0x05, 0x800c);
|
||||
//res |= i2c_write_reg(0x06, 0x3000);
|
||||
}
|
||||
if (mode == AC_MODULE_DAC || mode == AC_MODULE_ADC_DAC || mode == AC_MODULE_LINE) {
|
||||
//* Enable Headphone output 注意使用耳机时,最后开以下寄存器
|
||||
res |= i2c_write_reg(OMIXER_DACA_CTRL, 0xff80);
|
||||
res |= i2c_write_reg(HPOUT_CTRL, 0xc3c1);
|
||||
res |= i2c_write_reg(HPOUT_CTRL, 0xcb00);
|
||||
// huh?
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
res |= i2c_write_reg(HPOUT_CTRL, 0xfbc0);
|
||||
|
||||
//* Enable Speaker output
|
||||
res |= i2c_write_reg(SPKOUT_CTRL, 0xeabd);
|
||||
// huh?
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
ac101_set_earph_volume(255);
|
||||
ac101_set_spk_volume(255);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t ac101_stop(void) {
|
||||
esp_err_t res = 0;
|
||||
res |= i2c_write_reg(HPOUT_CTRL, 0x01); //disable earphone
|
||||
res |= i2c_write_reg(SPKOUT_CTRL, 0xe880); //disable speaker
|
||||
return res;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t ac101_deinit(void) {
|
||||
return i2c_write_reg(CHIP_AUDIO_RS, 0x123); //soft reset
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* Don't know when this one is supposed to be called
|
||||
*/
|
||||
esp_err_t AC101_i2s_config_clock(ac_i2s_clock_t *cfg) {
|
||||
esp_err_t res = 0;
|
||||
uint16_t regval=0;
|
||||
regval = i2c_read_reg(I2S1LCK_CTRL);
|
||||
regval &= 0xe03f;
|
||||
regval |= (cfg->bclk_div << 9);
|
||||
regval |= (cfg->lclk_div << 6);
|
||||
res = i2c_write_reg(I2S1LCK_CTRL, regval);
|
||||
return res;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t ac101_get_voice_volume(int* volume) {
|
||||
*volume = ac101_get_earph_volume();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
void ac101_pa_power(bool enable) {
|
||||
gpio_config_t io_conf;
|
||||
memset(&io_conf, 0, sizeof(io_conf));
|
||||
io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = BIT(GPIO_PA_EN);
|
||||
io_conf.pull_down_en = 0;
|
||||
io_conf.pull_up_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
if (enable) {
|
||||
gpio_set_level(GPIO_PA_EN, 1);
|
||||
} else {
|
||||
gpio_set_level(GPIO_PA_EN, 0);
|
||||
}
|
||||
}
|
||||
*/
|
||||
176
components/squeezelite/a1s/ac101.h
Normal file
176
components/squeezelite/a1s/ac101.h
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* ESPRESSIF MIT License
|
||||
*
|
||||
* Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD>
|
||||
*
|
||||
* Permission is hereby granted for use on all ESPRESSIF SYSTEMS products, in which case,
|
||||
* it is free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished
|
||||
* to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __AC101_H__
|
||||
#define __AC101_H__
|
||||
|
||||
#include "esp_types.h"
|
||||
|
||||
#define AC101_ADDR 0x1a /*!< Device address*/
|
||||
|
||||
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
|
||||
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
|
||||
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
|
||||
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
|
||||
#define ACK_VAL 0x0 /*!< I2C ack value */
|
||||
#define NACK_VAL 0x1 /*!< I2C nack value */
|
||||
|
||||
#define CHIP_AUDIO_RS 0x00
|
||||
#define PLL_CTRL1 0x01
|
||||
#define PLL_CTRL2 0x02
|
||||
#define SYSCLK_CTRL 0x03
|
||||
#define MOD_CLK_ENA 0x04
|
||||
#define MOD_RST_CTRL 0x05
|
||||
#define I2S_SR_CTRL 0x06
|
||||
#define I2S1LCK_CTRL 0x10
|
||||
#define I2S1_SDOUT_CTRL 0x11
|
||||
#define I2S1_SDIN_CTRL 0x12
|
||||
#define I2S1_MXR_SRC 0x13
|
||||
#define I2S1_VOL_CTRL1 0x14
|
||||
#define I2S1_VOL_CTRL2 0x15
|
||||
#define I2S1_VOL_CTRL3 0x16
|
||||
#define I2S1_VOL_CTRL4 0x17
|
||||
#define I2S1_MXR_GAIN 0x18
|
||||
#define ADC_DIG_CTRL 0x40
|
||||
#define ADC_VOL_CTRL 0x41
|
||||
#define HMIC_CTRL1 0x44
|
||||
#define HMIC_CTRL2 0x45
|
||||
#define HMIC_STATUS 0x46
|
||||
#define DAC_DIG_CTRL 0x48
|
||||
#define DAC_VOL_CTRL 0x49
|
||||
#define DAC_MXR_SRC 0x4c
|
||||
#define DAC_MXR_GAIN 0x4d
|
||||
#define ADC_APC_CTRL 0x50
|
||||
#define ADC_SRC 0x51
|
||||
#define ADC_SRCBST_CTRL 0x52
|
||||
#define OMIXER_DACA_CTRL 0x53
|
||||
#define OMIXER_SR 0x54
|
||||
#define OMIXER_BST1_CTRL 0x55
|
||||
#define HPOUT_CTRL 0x56
|
||||
#define SPKOUT_CTRL 0x58
|
||||
#define AC_DAC_DAPCTRL 0xa0
|
||||
#define AC_DAC_DAPHHPFC 0xa1
|
||||
#define AC_DAC_DAPLHPFC 0xa2
|
||||
#define AC_DAC_DAPLHAVC 0xa3
|
||||
#define AC_DAC_DAPLLAVC 0xa4
|
||||
#define AC_DAC_DAPRHAVC 0xa5
|
||||
#define AC_DAC_DAPRLAVC 0xa6
|
||||
#define AC_DAC_DAPHGDEC 0xa7
|
||||
#define AC_DAC_DAPLGDEC 0xa8
|
||||
#define AC_DAC_DAPHGATC 0xa9
|
||||
#define AC_DAC_DAPLGATC 0xaa
|
||||
#define AC_DAC_DAPHETHD 0xab
|
||||
#define AC_DAC_DAPLETHD 0xac
|
||||
#define AC_DAC_DAPHGKPA 0xad
|
||||
#define AC_DAC_DAPLGKPA 0xae
|
||||
#define AC_DAC_DAPHGOPA 0xaf
|
||||
#define AC_DAC_DAPLGOPA 0xb0
|
||||
#define AC_DAC_DAPOPT 0xb1
|
||||
#define DAC_DAP_ENA 0xb5
|
||||
|
||||
typedef enum{
|
||||
SAMPLE_RATE_8000 = 0x0000,
|
||||
SAMPLE_RATE_11052 = 0x1000,
|
||||
SAMPLE_RATE_12000 = 0x2000,
|
||||
SAMPLE_RATE_16000 = 0x3000,
|
||||
SAMPLE_RATE_22050 = 0x4000,
|
||||
SAMPLE_RATE_24000 = 0x5000,
|
||||
SAMPLE_RATE_32000 = 0x6000,
|
||||
SAMPLE_RATE_44100 = 0x7000,
|
||||
SAMPLE_RATE_48000 = 0x8000,
|
||||
SAMPLE_RATE_96000 = 0x9000,
|
||||
SAMPLE_RATE_192000 = 0xa000,
|
||||
} ac_adda_fs_i2s1_t;
|
||||
|
||||
typedef enum{
|
||||
BCLK_DIV_1 = 0x0,
|
||||
BCLK_DIV_2 = 0x1,
|
||||
BCLK_DIV_4 = 0x2,
|
||||
BCLK_DIV_6 = 0x3,
|
||||
BCLK_DIV_8 = 0x4,
|
||||
BCLK_DIV_12 = 0x5,
|
||||
BCLK_DIV_16 = 0x6,
|
||||
BCLK_DIV_24 = 0x7,
|
||||
BCLK_DIV_32 = 0x8,
|
||||
BCLK_DIV_48 = 0x9,
|
||||
BCLK_DIV_64 = 0xa,
|
||||
BCLK_DIV_96 = 0xb,
|
||||
BCLK_DIV_128 = 0xc,
|
||||
BCLK_DIV_192 = 0xd,
|
||||
} ac_i2s1_bclk_div_t;
|
||||
|
||||
typedef enum{
|
||||
LRCK_DIV_16 =0x0,
|
||||
LRCK_DIV_32 =0x1,
|
||||
LRCK_DIV_64 =0x2,
|
||||
LRCK_DIV_128 =0x3,
|
||||
LRCK_DIV_256 =0x4,
|
||||
} ac_i2s1_lrck_div_t;
|
||||
|
||||
typedef enum {
|
||||
BIT_LENGTH_8_BITS = 0x00,
|
||||
BIT_LENGTH_16_BITS = 0x01,
|
||||
BIT_LENGTH_20_BITS = 0x02,
|
||||
BIT_LENGTH_24_BITS = 0x03,
|
||||
} ac_bits_length_t;
|
||||
|
||||
typedef enum {
|
||||
AC_MODE_MIN = -1,
|
||||
AC_MODE_SLAVE = 0x00,
|
||||
AC_MODE_MASTER = 0x01,
|
||||
AC_MODE_MAX,
|
||||
} ac_mode_sm_t;
|
||||
|
||||
typedef enum {
|
||||
AC_MODULE_MIN = -1,
|
||||
AC_MODULE_ADC = 0x01,
|
||||
AC_MODULE_DAC = 0x02,
|
||||
AC_MODULE_ADC_DAC = 0x03,
|
||||
AC_MODULE_LINE = 0x04,
|
||||
AC_MODULE_MAX
|
||||
} ac_module_t;
|
||||
|
||||
typedef enum{
|
||||
SRC_MIC1 = 1,
|
||||
SRC_MIC2 = 2,
|
||||
SRC_LINEIN = 3,
|
||||
}ac_output_mixer_source_t;
|
||||
|
||||
typedef enum {
|
||||
GAIN_N45DB = 0,
|
||||
GAIN_N30DB = 1,
|
||||
GAIN_N15DB = 2,
|
||||
GAIN_0DB = 3,
|
||||
GAIN_15DB = 4,
|
||||
GAIN_30DB = 5,
|
||||
GAIN_45DB = 6,
|
||||
GAIN_60DB = 7,
|
||||
} ac_output_mixer_gain_t;
|
||||
|
||||
typedef struct {
|
||||
ac_i2s1_bclk_div_t bclk_div; /*!< bits clock divide */
|
||||
ac_i2s1_lrck_div_t lclk_div; /*!< WS clock divide */
|
||||
} ac_i2s_clock_t;
|
||||
|
||||
#endif
|
||||
38
components/squeezelite/adac.h
Normal file
38
components/squeezelite/adac.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/i2s.h"
|
||||
|
||||
typedef enum { ADAC_ON = 0, ADAC_STANDBY, ADAC_OFF } adac_power_e;
|
||||
|
||||
struct adac_s {
|
||||
bool (*init)(int i2c_port_num, int i2s_num, i2s_config_t *config);
|
||||
void (*deinit)(void);
|
||||
void (*power)(adac_power_e mode);
|
||||
void (*speaker)(bool active);
|
||||
void (*headset)(bool active);
|
||||
void (*volume)(unsigned left, unsigned right);
|
||||
};
|
||||
|
||||
extern struct adac_s dac_tas57xx;
|
||||
extern struct adac_s dac_a1s;
|
||||
extern struct adac_s dac_null;
|
||||
@@ -19,5 +19,6 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
|
||||
|
||||
# -I$(COMPONENT_PATH)/../codecs/inc/faad2
|
||||
|
||||
|
||||
COMPONENT_SRCDIRS := . tas57xx a1s null
|
||||
COMPONENT_ADD_INCLUDEDIRS := . ./tas57xx ./a1s
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated);
|
||||
raop_sync.error[raop_sync.idx] = (raop_sync.playtime - now) - ms;
|
||||
sync_nb = SYNC_NB;
|
||||
LOG_INFO("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]);
|
||||
LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]);
|
||||
LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,10 +137,16 @@ static void scroll_task(void* arg);
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void sb_display_init(void) {
|
||||
bool sb_display_init(void) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[SCROLL_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
|
||||
// no display, just make sure we won't have requests
|
||||
if (!display || display->height == 0 || display->width == 0) {
|
||||
LOG_INFO("no display for LMS");
|
||||
return false;
|
||||
}
|
||||
|
||||
// need to force height to 32 maximum
|
||||
display_width = display->width;
|
||||
display_height = min(display->height, 32);
|
||||
@@ -163,6 +169,8 @@ void sb_display_init(void) {
|
||||
|
||||
notify_chain = server_notify;
|
||||
server_notify = server;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -288,7 +296,7 @@ static void show_display_buffer(char *ddram) {
|
||||
makeprintable((unsigned char *)line1);
|
||||
makeprintable((unsigned char *)line2);
|
||||
|
||||
LOG_INFO("\n\t%.40s\n\t%.40s", line1, line2);
|
||||
LOG_DEBUG("\n\t%.40s\n\t%.40s", line1, line2);
|
||||
|
||||
display->line(1, DISPLAY_LEFT, DISPLAY_CLEAR, line1);
|
||||
display->line(2, DISPLAY_LEFT, DISPLAY_CLEAR | DISPLAY_UPDATE, line2);
|
||||
@@ -380,7 +388,7 @@ static void grfs_handler(u8_t *data, int len) {
|
||||
int size = len - sizeof(struct grfs_packet);
|
||||
int offset = htons(pkt->offset);
|
||||
|
||||
LOG_INFO("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu",
|
||||
LOG_DEBUG("gfrs s:%u d:%u p:%u sp:%u by:%hu m:%hu w:%hu o:%hu",
|
||||
(int) pkt->screen,
|
||||
(int) pkt->direction, // 1=left, 2=right
|
||||
htonl(pkt->pause), // in ms
|
||||
@@ -425,7 +433,7 @@ static void grfs_handler(u8_t *data, int len) {
|
||||
static void grfg_handler(u8_t *data, int len) {
|
||||
struct grfg_packet *pkt = (struct grfg_packet*) data;
|
||||
|
||||
LOG_INFO("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
|
||||
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
|
||||
|
||||
memcpy(scroller.back_frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
|
||||
scroller.window_width = htons(pkt->width);
|
||||
@@ -457,7 +465,7 @@ static void grfg_handler(u8_t *data, int len) {
|
||||
}
|
||||
else {
|
||||
// if we just got a content update, let the scroller manage the screen
|
||||
LOG_INFO("resuming scrolling task");
|
||||
LOG_DEBUG("resuming scrolling task");
|
||||
vTaskResume(scroller.task);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,11 @@ uint32_t _gettime_ms_(void) {
|
||||
}
|
||||
|
||||
extern void sb_controls_init(void);
|
||||
extern void sb_display_init(void);
|
||||
extern bool sb_display_init(void);
|
||||
|
||||
u8_t custom_player_id = 12;
|
||||
|
||||
void embedded_init(void) {
|
||||
sb_controls_init();
|
||||
sb_display_init();
|
||||
if (sb_display_init()) custom_player_id = 100;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
#define OUTPUT_THREAD_STACK_SIZE 6 * 1024
|
||||
#define IR_THREAD_STACK_SIZE 6 * 1024
|
||||
|
||||
#define PLAYER_ID 100
|
||||
// or can be as simple as #define PLAYER_ID 100
|
||||
#define PLAYER_ID custom_player_id;
|
||||
extern u8_t custom_player_id;
|
||||
|
||||
#define BASE_CAP "Model=squeezeesp32,AccuratePlayPoints=1,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
|
||||
#define EXT_BSS __attribute__((section(".ext_ram.bss")))
|
||||
|
||||
|
||||
@@ -52,8 +52,10 @@ static void usage(const char *argv0) {
|
||||
printf(TITLE " See -t for license terms\n"
|
||||
"Usage: %s [options]\n"
|
||||
" -s <server>[:<port>]\tConnect to specified server, otherwise uses autodiscovery to find server\n"
|
||||
#if !EMBEDDED
|
||||
" -o <output device>\tSpecify output device, default \"default\", - = output to stdout\n"
|
||||
" -l \t\t\tList output devices\n"
|
||||
#endif
|
||||
#if ALSA
|
||||
" -a <b>:<p>:<f>:<m>\tSpecify ALSA params to open output device, b = buffer time in ms or size in bytes, p = period count or size in bytes, f sample format (16|24|24_3|32), m = use mmap (0|1)\n"
|
||||
#endif
|
||||
@@ -535,7 +537,7 @@ int main(int argc, char **argv) {
|
||||
pidfile = optarg;
|
||||
break;
|
||||
#endif
|
||||
#ifndef EMBEDDED
|
||||
#if !EMBEDDED
|
||||
case 'l':
|
||||
list_devices();
|
||||
exit(0);
|
||||
|
||||
32
components/squeezelite/null/dac_null.c
Normal file
32
components/squeezelite/null/dac_null.c
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "adac.h"
|
||||
|
||||
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config) { return true; };
|
||||
static void deinit(void) { };
|
||||
static void speaker(bool active) { };
|
||||
static void headset(bool active) { } ;
|
||||
static void volume(unsigned left, unsigned right) { };
|
||||
static void power(adac_power_e mode) { };
|
||||
|
||||
struct adac_s dac_null = { init, deinit, power, speaker, headset, volume };
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "squeezelite.h"
|
||||
#include "perf_trace.h"
|
||||
#include "config.h"
|
||||
|
||||
extern struct outputstate output;
|
||||
extern struct buffer *outputbuf;
|
||||
@@ -45,6 +46,7 @@ static log_level loglevel;
|
||||
static bool running = false;
|
||||
static uint8_t *btout;
|
||||
static frames_t oframes;
|
||||
static bool stats;
|
||||
|
||||
static int _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);
|
||||
@@ -68,15 +70,13 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
||||
DECLARE_ALL_MIN_MAX;
|
||||
|
||||
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
|
||||
#ifdef CONFIG_SQUEEZEAMP
|
||||
gpio_pad_select_gpio(config_spdif_gpio);
|
||||
gpio_set_direction(config_spdif_gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(config_spdif_gpio, 0);
|
||||
#endif
|
||||
loglevel = level;
|
||||
running = true;
|
||||
output.write_cb = &_write_frames;
|
||||
hal_bluetooth_init(device);
|
||||
char *p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
|
||||
stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
free(p);
|
||||
}
|
||||
|
||||
void output_close_bt(void) {
|
||||
@@ -166,12 +166,12 @@ void output_bt_tick(void) {
|
||||
static time_t lastTime=0;
|
||||
|
||||
if (!running) return;
|
||||
|
||||
|
||||
LOCK_S;
|
||||
SET_MIN_MAX_SIZED(_buf_used(streambuf), stream_buf, streambuf->size);
|
||||
UNLOCK_S;
|
||||
|
||||
if (lastTime <= gettime_ms() )
|
||||
if (stats && lastTime <= gettime_ms() )
|
||||
{
|
||||
lastTime = gettime_ms() + STATS_REPORT_DELAY_MS;
|
||||
LOG_INFO("Statistics over %u secs. " , STATS_REPORT_DELAY_MS/1000);
|
||||
|
||||
@@ -47,46 +47,18 @@ sure that using rate_delay would fix that
|
||||
#include "driver/gpio.h"
|
||||
#include "perf_trace.h"
|
||||
#include <signal.h>
|
||||
#include "adac.h"
|
||||
#include "time.h"
|
||||
#include "led.h"
|
||||
#include "monitor.h"
|
||||
#include "config.h"
|
||||
#include "accessors.h"
|
||||
|
||||
#define LOCK mutex_lock(outputbuf->mutex)
|
||||
#define UNLOCK mutex_unlock(outputbuf->mutex)
|
||||
|
||||
#define FRAME_BLOCK MAX_SILENCE_FRAMES
|
||||
|
||||
// Prevent compile errors if dac output is
|
||||
// included in the build and not actually activated in menuconfig
|
||||
#ifndef CONFIG_I2S_BCK_IO
|
||||
#define CONFIG_I2S_BCK_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_I2S_WS_IO
|
||||
#define CONFIG_I2S_WS_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_I2S_DO_IO
|
||||
#define CONFIG_I2S_DO_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_I2S_NUM
|
||||
#define CONFIG_I2S_NUM -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_SPDIF_BCK_IO
|
||||
#define CONFIG_SPDIF_BCK_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_SPDIF_WS_IO
|
||||
#define CONFIG_SPDIF_WS_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_SPDIF_DO_IO
|
||||
#define CONFIG_SPDIF_DO_IO -1
|
||||
#endif
|
||||
#ifndef CONFIG_SPDIF_NUM
|
||||
#define CONFIG_SPDIF_NUM -1
|
||||
#endif
|
||||
|
||||
typedef enum { DAC_ACTIVE = 0, DAC_STANDBY, DAC_DOWN, DAC_ANALOGUE_OFF, DAC_ANALOGUE_ON, DAC_VOLUME } dac_cmd_e;
|
||||
|
||||
// must have an integer ratio with FRAME_BLOCK (see spdif comment)
|
||||
#define DMA_BUF_LEN 512
|
||||
#define DMA_BUF_COUNT 12
|
||||
@@ -106,48 +78,43 @@ typedef enum { DAC_ACTIVE = 0, DAC_STANDBY, DAC_DOWN, DAC_ANALOGUE_OFF, DAC_ANAL
|
||||
RESET_MIN_MAX(buffering);
|
||||
|
||||
#define STATS_PERIOD_MS 5000
|
||||
#define STAT_STACK_SIZE (3*1024)
|
||||
|
||||
extern struct outputstate output;
|
||||
extern struct buffer *streambuf;
|
||||
extern struct buffer *outputbuf;
|
||||
extern u8_t *silencebuf;
|
||||
|
||||
// by default no DAC selected
|
||||
struct adac_s *adac = &dac_null;
|
||||
|
||||
static log_level loglevel;
|
||||
|
||||
static bool jack_mutes_amp;
|
||||
static bool running, isI2SStarted;
|
||||
static i2s_config_t i2s_config;
|
||||
static int bytes_per_frame;
|
||||
static thread_type thread, stats_thread;
|
||||
static u8_t *obuf;
|
||||
static frames_t oframes;
|
||||
static bool spdif;
|
||||
static size_t dma_buf_frames;
|
||||
static pthread_t thread;
|
||||
static TaskHandle_t stats_task;
|
||||
static bool stats;
|
||||
static int amp_gpio = -1;
|
||||
|
||||
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();
|
||||
static void *output_thread_i2s_stats();
|
||||
static void dac_cmd(dac_cmd_e cmd, ...);
|
||||
static int tas57_detect(void);
|
||||
static void *output_thread_i2s(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);
|
||||
|
||||
// force all GPIOs to what we need
|
||||
#ifdef CONFIG_SQUEEZEAMP
|
||||
|
||||
#define TAS57xx
|
||||
|
||||
#undef CONFIG_I2S_BCK_IO
|
||||
#define CONFIG_I2S_BCK_IO 33
|
||||
#undef CONFIG_I2S_WS_IO
|
||||
#define CONFIG_I2S_WS_IO 25
|
||||
#undef CONFIG_I2S_DO_IO
|
||||
#define CONFIG_I2S_DO_IO 32
|
||||
#undef CONFIG_I2S_NUM
|
||||
#define CONFIG_I2S_NUM 0
|
||||
|
||||
#undef CONFIG_SPDIF_BCK_IO
|
||||
#define CONFIG_SPDIF_BCK_IO 33
|
||||
#undef CONFIG_SPDIF_WS_IO
|
||||
@@ -156,51 +123,15 @@ static void (*jack_handler_chain)(bool inserted);
|
||||
#define CONFIG_SPDIF_DO_IO 15
|
||||
#undef CONFIG_SPDIF_NUM
|
||||
#define CONFIG_SPDIF_NUM 0
|
||||
#undef CONFIG_I2S_NUM
|
||||
#define CONFIG_I2S_NUM 0
|
||||
#elif defined CONFIG_A1S
|
||||
#define A1S
|
||||
#undef CONFIG_I2S_NUM
|
||||
#define CONFIG_I2S_NUM 0
|
||||
#endif
|
||||
|
||||
#define I2C_PORT 0
|
||||
#define VOLUME_GPIO 14
|
||||
|
||||
#define TAS575x 0x98
|
||||
#define TAS578x 0x90
|
||||
|
||||
struct tas57xx_cmd_s {
|
||||
u8_t reg;
|
||||
u8_t value;
|
||||
};
|
||||
|
||||
u8_t config_spdif_gpio = CONFIG_SPDIF_DO_IO;
|
||||
|
||||
static const struct tas57xx_cmd_s tas57xx_init_sequence[] = {
|
||||
{ 0x00, 0x00 }, // select page 0
|
||||
{ 0x02, 0x10 }, // standby
|
||||
{ 0x0d, 0x10 }, // use SCK for PLL
|
||||
{ 0x25, 0x08 }, // ignore SCK halt
|
||||
{ 0x08, 0x10 }, // Mute control enable (from TAS5780)
|
||||
{ 0x54, 0x02 }, // Mute output control (from TAS5780)
|
||||
{ 0x02, 0x00 }, // restart
|
||||
{ 0xff, 0xff } // end of table
|
||||
};
|
||||
|
||||
static const i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = 27,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = 26,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 100000,
|
||||
};
|
||||
|
||||
static const struct tas57xx_cmd_s tas57xx_cmd[] = {
|
||||
{ 0x02, 0x00 }, // DAC_ACTIVE
|
||||
{ 0x02, 0x10 }, // DAC_STANDBY
|
||||
{ 0x02, 0x01 }, // DAC_DOWN
|
||||
{ 0x56, 0x10 }, // DAC_ANALOGUE_OFF
|
||||
{ 0x56, 0x00 }, // DAC_ANALOGUE_ON
|
||||
};
|
||||
|
||||
static u8_t tas57_addr;
|
||||
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
* jack insertion handler
|
||||
@@ -209,12 +140,33 @@ static void jack_handler(bool inserted) {
|
||||
// jack detection bounces a bit but that seems fine
|
||||
if (jack_mutes_amp) {
|
||||
LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
|
||||
if (inserted) dac_cmd(DAC_ANALOGUE_OFF);
|
||||
else dac_cmd(DAC_ANALOGUE_ON);
|
||||
if (inserted) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
}
|
||||
|
||||
// activate headset
|
||||
if (inserted) adac->headset(true);
|
||||
else adac->headset(false);
|
||||
|
||||
// and chain if any
|
||||
if (jack_handler_chain) (jack_handler_chain)(inserted);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* amp GPIO
|
||||
*/
|
||||
void set_amp_gpio(int gpio, char *value) {
|
||||
if (!strcasecmp(value, "amp")) {
|
||||
amp_gpio = gpio;
|
||||
|
||||
gpio_pad_select_gpio(amp_gpio);
|
||||
gpio_set_direction(amp_gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(amp_gpio, 0);
|
||||
|
||||
LOG_INFO("setting amplifier GPIO %d", amp_gpio);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Initialize the DAC output
|
||||
*/
|
||||
@@ -226,44 +178,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
jack_mutes_amp = (strcmp(p,"1") == 0 ||strcasecmp(p,"y") == 0);
|
||||
free(p);
|
||||
|
||||
#ifdef TAS57xx
|
||||
LOG_INFO("Initializing TAS57xx ");
|
||||
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0);
|
||||
|
||||
// init volume & mute
|
||||
gpio_pad_select_gpio(VOLUME_GPIO);
|
||||
gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(VOLUME_GPIO, 0);
|
||||
|
||||
// configure i2c
|
||||
i2c_param_config(I2C_PORT, &i2c_config);
|
||||
i2c_driver_install(I2C_PORT, I2C_MODE_MASTER, false, false, false);
|
||||
|
||||
// find which TAS we are using
|
||||
tas57_addr = tas57_detect();
|
||||
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) {
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
|
||||
|
||||
LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
|
||||
}
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
LOG_ERROR("could not intialize TAS57xx %d", ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_I2S_BITS_PER_CHANNEL
|
||||
switch (CONFIG_I2S_BITS_PER_CHANNEL) {
|
||||
case 24:
|
||||
@@ -287,8 +201,6 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
bytes_per_frame = 2*2;
|
||||
#endif
|
||||
|
||||
if (strcasestr(device, "spdif")) spdif = true;
|
||||
|
||||
output.write_cb = &_i2s_write_frames;
|
||||
obuf = malloc(FRAME_BLOCK * bytes_per_frame);
|
||||
if (!obuf) {
|
||||
@@ -296,12 +208,20 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
return;
|
||||
}
|
||||
|
||||
running=true;
|
||||
running = true;
|
||||
|
||||
i2s_pin_config_t pin_config;
|
||||
|
||||
if (spdif) {
|
||||
pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO,
|
||||
// common I2S initialization
|
||||
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
|
||||
// in case of overflow, do not replay old buffer
|
||||
i2s_config.tx_desc_auto_clear = true;
|
||||
i2s_config.use_apll = true;
|
||||
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
||||
|
||||
if (strcasestr(device, "spdif")) {
|
||||
spdif = true;
|
||||
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_SPDIF_BCK_IO, .ws_io_num = CONFIG_SPDIF_WS_IO,
|
||||
.data_out_num = CONFIG_SPDIF_DO_IO, .data_in_num = -1 //Not used
|
||||
};
|
||||
i2s_config.sample_rate = output.current_sample_rate * 2;
|
||||
@@ -315,49 +235,47 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
audio frame. So the real depth is true frames is (LEN * COUNT / 2)
|
||||
*/
|
||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;
|
||||
i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
i2s_set_pin(CONFIG_I2S_NUM, &i2s_pin_config);
|
||||
LOG_INFO("SPDIF using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
|
||||
} else {
|
||||
pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
|
||||
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
|
||||
};
|
||||
#ifdef TAS57xx
|
||||
gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
|
||||
gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(CONFIG_SPDIF_DO_IO, 0);
|
||||
adac = &dac_tas57xx;
|
||||
#elif defined(A1S)
|
||||
adac = &dac_a1s;
|
||||
#endif
|
||||
i2s_config.sample_rate = output.current_sample_rate;
|
||||
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
|
||||
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
|
||||
i2s_config.dma_buf_len = DMA_BUF_LEN;
|
||||
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN;
|
||||
#ifdef TAS57xx
|
||||
gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
|
||||
gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(CONFIG_SPDIF_DO_IO, 0);
|
||||
#endif
|
||||
|
||||
// finally let DAC driver initialize I2C and I2S
|
||||
adac->init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config);
|
||||
}
|
||||
|
||||
i2s_config.mode = I2S_MODE_MASTER | I2S_MODE_TX;
|
||||
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
|
||||
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
|
||||
// in case of overflow, do not replay old buffer
|
||||
i2s_config.tx_desc_auto_clear = true;
|
||||
i2s_config.use_apll = true;
|
||||
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
||||
|
||||
LOG_INFO("Initializing I2S mode %s with rate: %d, bits per sample: %d, buffer frames: %d, number of buffers: %d ",
|
||||
spdif ? "S/PDIF" : "normal",
|
||||
i2s_config.sample_rate, i2s_config.bits_per_sample, i2s_config.dma_buf_len, i2s_config.dma_buf_count);
|
||||
|
||||
i2s_driver_install(CONFIG_I2S_NUM, &i2s_config, 0, NULL);
|
||||
i2s_set_pin(CONFIG_I2S_NUM, &pin_config);
|
||||
|
||||
i2s_stop(CONFIG_I2S_NUM);
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
isI2SStarted=false;
|
||||
|
||||
dac_cmd(DAC_STANDBY);
|
||||
adac->power(ADAC_STANDBY);
|
||||
|
||||
jack_handler_chain = jack_handler_svc;
|
||||
jack_handler_svc = jack_handler;
|
||||
|
||||
if (jack_mutes_amp && jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_OFF);
|
||||
else dac_cmd(DAC_ANALOGUE_ON);
|
||||
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
|
||||
parse_set_GPIO(set_amp_gpio);
|
||||
|
||||
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
|
||||
|
||||
cfg.thread_name= "output_i2s";
|
||||
@@ -367,11 +285,17 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
esp_pthread_set_cfg(&cfg);
|
||||
pthread_create(&thread, NULL, output_thread_i2s, NULL);
|
||||
|
||||
cfg.thread_name= "output_i2s_sts";
|
||||
cfg.prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT - 1;
|
||||
cfg.stack_size = 2048;
|
||||
esp_pthread_set_cfg(&cfg);
|
||||
pthread_create(&stats_thread, NULL, output_thread_i2s_stats, NULL);
|
||||
// do we want stats
|
||||
p = config_alloc_get_default(NVS_TYPE_STR, "stats", "n", 0);
|
||||
stats = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
free(p);
|
||||
|
||||
// memory still used but at least task is not created
|
||||
if (stats) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -383,27 +307,20 @@ void output_close_i2s(void) {
|
||||
running = false;
|
||||
UNLOCK;
|
||||
pthread_join(thread, NULL);
|
||||
pthread_join(stats_thread, NULL);
|
||||
if (stats) vTaskDelete(stats_task);
|
||||
|
||||
i2s_driver_uninstall(CONFIG_I2S_NUM);
|
||||
free(obuf);
|
||||
|
||||
#ifdef TAS57xx
|
||||
i2c_driver_delete(I2C_PORT);
|
||||
#endif
|
||||
adac->deinit();
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
bool output_volume_i2s(unsigned left, unsigned right) {
|
||||
#ifdef TAS57xx
|
||||
if (!spdif) {
|
||||
LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right);
|
||||
gpio_set_level(VOLUME_GPIO, left || right);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
adac->volume(left, right);
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -456,7 +373,7 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
||||
/****************************************************************************************
|
||||
* Main output thread
|
||||
*/
|
||||
static void *output_thread_i2s() {
|
||||
static void *output_thread_i2s(void *arg) {
|
||||
size_t count = 0, bytes;
|
||||
frames_t iframes = FRAME_BLOCK;
|
||||
uint32_t timer_start = 0;
|
||||
@@ -480,16 +397,15 @@ static void *output_thread_i2s() {
|
||||
// manage led display & analogue
|
||||
if (state != output.state) {
|
||||
LOG_INFO("Output state is %d", output.state);
|
||||
if (output.state == OUTPUT_OFF) led_blink(LED_GREEN, 100, 2500);
|
||||
else if (output.state == OUTPUT_STOPPED) {
|
||||
#ifdef TAS57xx
|
||||
dac_cmd(DAC_ANALOGUE_OFF);
|
||||
#endif
|
||||
if (output.state == OUTPUT_OFF) {
|
||||
led_blink(LED_GREEN, 100, 2500);
|
||||
if (amp_gpio != -1) gpio_set_level(amp_gpio, 0);
|
||||
LOG_INFO("switching off amp GPIO %d", amp_gpio);
|
||||
} else if (output.state == OUTPUT_STOPPED) {
|
||||
adac->speaker(false);
|
||||
led_blink(LED_GREEN, 200, 1000);
|
||||
} else if (output.state == OUTPUT_RUNNING) {
|
||||
#ifdef TAS57xx
|
||||
if (!jack_mutes_amp || !jack_inserted_svc()) dac_cmd(DAC_ANALOGUE_ON);
|
||||
#endif
|
||||
if (!jack_mutes_amp || !jack_inserted_svc()) adac->speaker(true);
|
||||
led_on(LED_GREEN);
|
||||
}
|
||||
}
|
||||
@@ -500,7 +416,7 @@ static void *output_thread_i2s() {
|
||||
if (isI2SStarted) {
|
||||
isI2SStarted = false;
|
||||
i2s_stop(CONFIG_I2S_NUM);
|
||||
if (!spdif) dac_cmd(DAC_STANDBY);
|
||||
adac->power(ADAC_STANDBY);
|
||||
count = 0;
|
||||
}
|
||||
usleep(200000);
|
||||
@@ -546,7 +462,8 @@ static void *output_thread_i2s() {
|
||||
LOG_INFO("Restarting I2S.");
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
i2s_start(CONFIG_I2S_NUM);
|
||||
if (!spdif) dac_cmd(DAC_ACTIVE);
|
||||
adac->power(ADAC_ON);
|
||||
if (amp_gpio != -1) gpio_set_level(amp_gpio, 1);
|
||||
}
|
||||
|
||||
// this does not work well as set_sample_rates resets the fifos (and it's too early)
|
||||
@@ -591,12 +508,12 @@ static void *output_thread_i2s() {
|
||||
/****************************************************************************************
|
||||
* Stats output thread
|
||||
*/
|
||||
static void *output_thread_i2s_stats() {
|
||||
while (running) {
|
||||
LOCK;
|
||||
static void *output_thread_i2s_stats(void *arg) {
|
||||
while (1) {
|
||||
// no need to lock
|
||||
output_state state = output.state;
|
||||
UNLOCK;
|
||||
if(state>OUTPUT_STOPPED){
|
||||
|
||||
if(stats && state>OUTPUT_STOPPED){
|
||||
LOG_INFO( "Output State: %d, current sample rate: %d, bytes per frame: %d",state,output.current_sample_rate, bytes_per_frame);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD1);
|
||||
LOG_INFO( LINE_MIN_MAX_FORMAT_HEAD2);
|
||||
@@ -616,77 +533,11 @@ static void *output_thread_i2s_stats() {
|
||||
LOG_INFO(" ----------+----------+-----------+-----------+");
|
||||
RESET_ALL_MIN_MAX;
|
||||
}
|
||||
usleep(STATS_PERIOD_MS *1000);
|
||||
vTaskDelay( pdMS_TO_TICKS( STATS_PERIOD_MS ) );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* DAC specific commands
|
||||
*/
|
||||
void dac_cmd(dac_cmd_e cmd, ...) {
|
||||
va_list args;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
va_start(args, cmd);
|
||||
#ifdef TAS57xx
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
switch(cmd) {
|
||||
case DAC_VOLUME:
|
||||
LOG_ERROR("volume not handled yet");
|
||||
break;
|
||||
default:
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
}
|
||||
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
LOG_ERROR("could not intialize TAS57xx %d", ret);
|
||||
}
|
||||
#endif
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* TAS57 detection
|
||||
*/
|
||||
#ifdef TAS57xx
|
||||
static int tas57_detect(void) {
|
||||
u8_t data, addr[] = {TAS578x, TAS575x};
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < sizeof(addr); i++) {
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(I2C_PORT, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
LOG_INFO("Detected TAS @0x%x", addr[i]);
|
||||
return addr[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
* SPDIF support
|
||||
*/
|
||||
|
||||
@@ -45,7 +45,7 @@ static sockfd sock = -1;
|
||||
static in_addr_t slimproto_ip = 0;
|
||||
static u16_t slimproto_hport = 9000;
|
||||
static u16_t slimproto_cport = 9090;
|
||||
static u8_t player_id = PLAYER_ID;
|
||||
static u8_t player_id;
|
||||
|
||||
extern struct buffer *streambuf;
|
||||
extern struct buffer *outputbuf;
|
||||
@@ -284,22 +284,14 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
case 't':
|
||||
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
|
||||
break;
|
||||
case 'f':
|
||||
case 'q':
|
||||
decode_flush();
|
||||
if (!output.external) output_flush();
|
||||
status.frames_played = 0;
|
||||
stream_disconnect();
|
||||
sendSTAT("STMf", 0);
|
||||
buf_flush(streambuf);
|
||||
break;
|
||||
case 'f':
|
||||
decode_flush();
|
||||
if (!output.external) output_flush();
|
||||
status.frames_played = 0;
|
||||
if (stream_disconnect()) {
|
||||
sendSTAT("STMf", 0);
|
||||
}
|
||||
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);
|
||||
buf_flush(streambuf);
|
||||
output.stop_time = gettime_ms();
|
||||
break;
|
||||
case 'p':
|
||||
{
|
||||
|
||||
248
components/squeezelite/tas57xx/dac_57xx.c
Normal file
248
components/squeezelite/tas57xx/dac_57xx.c
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "squeezelite.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "adac.h"
|
||||
|
||||
#define VOLUME_GPIO 14
|
||||
#define TAS575x 0x98
|
||||
#define TAS578x 0x90
|
||||
|
||||
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *config);
|
||||
static void deinit(void);
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static void volume(unsigned left, unsigned right);
|
||||
static void power(adac_power_e mode);
|
||||
|
||||
struct adac_s dac_tas57xx = { init, deinit, power, speaker, headset, volume };
|
||||
|
||||
struct tas57xx_cmd_s {
|
||||
uint8_t reg;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
static const struct tas57xx_cmd_s tas57xx_init_sequence[] = {
|
||||
{ 0x00, 0x00 }, // select page 0
|
||||
{ 0x02, 0x10 }, // standby
|
||||
{ 0x0d, 0x10 }, // use SCK for PLL
|
||||
{ 0x25, 0x08 }, // ignore SCK halt
|
||||
{ 0x08, 0x10 }, // Mute control enable (from TAS5780)
|
||||
{ 0x54, 0x02 }, // Mute output control (from TAS5780)
|
||||
{ 0x02, 0x00 }, // restart
|
||||
{ 0xff, 0xff } // end of table
|
||||
};
|
||||
|
||||
// matching orders
|
||||
typedef enum { TAS57_ACTIVE = 0, TAS57_STANDBY, TAS57_DOWN, TAS57_ANALOGUE_OFF, TAS57_ANALOGUE_ON, TAS57_VOLUME } dac_cmd_e;
|
||||
|
||||
static const struct tas57xx_cmd_s tas57xx_cmd[] = {
|
||||
{ 0x02, 0x00 }, // TAS57_ACTIVE
|
||||
{ 0x02, 0x10 }, // TAS57_STANDBY
|
||||
{ 0x02, 0x01 }, // TAS57_DOWN
|
||||
{ 0x56, 0x10 }, // TAS57_ANALOGUE_OFF
|
||||
{ 0x56, 0x00 }, // TAS57_ANALOGUE_ON
|
||||
};
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
static u8_t tas57_addr;
|
||||
static int i2c_port;
|
||||
|
||||
static void dac_cmd(dac_cmd_e cmd, ...);
|
||||
static int tas57_detect(void);
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
|
||||
LOG_INFO("Initializing TAS57xx ");
|
||||
|
||||
i2c_port = i2c_port_num;
|
||||
|
||||
// init volume & mute
|
||||
gpio_pad_select_gpio(VOLUME_GPIO);
|
||||
gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(VOLUME_GPIO, 0);
|
||||
|
||||
// configure i2c
|
||||
i2c_config_t i2c_config = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = 27,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_io_num = 26,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = 100000,
|
||||
};
|
||||
i2c_param_config(i2c_port, &i2c_config);
|
||||
i2c_driver_install(i2c_port, I2C_MODE_MASTER, false, false, false);
|
||||
LOG_INFO("DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
|
||||
|
||||
// find which TAS we are using
|
||||
tas57_addr = tas57_detect();
|
||||
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) {
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_init_sequence[i].value, I2C_MASTER_NACK);
|
||||
|
||||
LOG_DEBUG("i2c write %x at %u", tas57xx_init_sequence[i].reg, tas57xx_init_sequence[i].value);
|
||||
}
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
// configure I2S pins & install driver
|
||||
i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { .bck_io_num = 33, .ws_io_num = 25,
|
||||
.data_out_num = 32, .data_in_num = -1 //Not used
|
||||
};
|
||||
i2s_driver_install(i2s_num, i2s_config, 0, NULL);
|
||||
i2s_set_pin(i2s_num, &i2s_pin_config);
|
||||
LOG_INFO("DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
LOG_ERROR("could not intialize TAS57xx %d", ret);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* init
|
||||
*/
|
||||
static void deinit(void) {
|
||||
i2c_driver_delete(i2c_port);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* change volume
|
||||
*/
|
||||
static void volume(unsigned left, unsigned right) {
|
||||
LOG_INFO("TAS57xx volume (L:%u R:%u)", left, right);
|
||||
gpio_set_level(VOLUME_GPIO, left || right);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* power
|
||||
*/
|
||||
static void power(adac_power_e mode) {
|
||||
switch(mode) {
|
||||
case ADAC_STANDBY:
|
||||
dac_cmd(TAS57_STANDBY);
|
||||
break;
|
||||
case ADAC_ON:
|
||||
dac_cmd(TAS57_ACTIVE);
|
||||
break;
|
||||
case ADAC_OFF:
|
||||
dac_cmd(TAS57_DOWN);
|
||||
break;
|
||||
default:
|
||||
LOG_WARN("unknown DAC command");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
if (active) dac_cmd(TAS57_ANALOGUE_ON);
|
||||
else dac_cmd(TAS57_ANALOGUE_OFF);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* headset
|
||||
*/
|
||||
static void headset(bool active) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* DAC specific commands
|
||||
*/
|
||||
void dac_cmd(dac_cmd_e cmd, ...) {
|
||||
va_list args;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
va_start(args, cmd);
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
switch(cmd) {
|
||||
case TAS57_VOLUME:
|
||||
LOG_ERROR("DAC volume not handled yet");
|
||||
break;
|
||||
default:
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, tas57_addr | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, tas57xx_cmd[cmd].value, I2C_MASTER_NACK);
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
}
|
||||
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
LOG_ERROR("could not intialize TAS57xx %d", ret);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* TAS57 detection
|
||||
*/
|
||||
static int tas57_detect(void) {
|
||||
u8_t data, addr[] = {TAS578x, TAS575x};
|
||||
int ret;
|
||||
|
||||
for (int i = 0; i < sizeof(addr); i++) {
|
||||
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(i2c_cmd, 00, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(i2c_cmd);
|
||||
i2c_master_write_byte(i2c_cmd, addr[i] | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(i2c_cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(i2c_cmd);
|
||||
ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 50 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(i2c_cmd);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
LOG_INFO("Detected TAS @0x%x", addr[i]);
|
||||
return addr[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
5
components/telnet/CMakeLists.txt
Normal file
5
components/telnet/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS "telnet.c"
|
||||
INCLUDE_DIRS .
|
||||
INCLUDE_DIRS . ../tools/
|
||||
|
||||
)
|
||||
15
components/telnet/component.mk
Normal file
15
components/telnet/component.mk
Normal file
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_SRCDIRS := .
|
||||
COMPONENT_SRCDIRS += ./libtelnet
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
COMPONENT_ADD_INCLUDEDIRS += ./libtelnet
|
||||
COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
|
||||
|
||||
1
components/telnet/libtelnet
Submodule
1
components/telnet/libtelnet
Submodule
Submodule components/telnet/libtelnet added at 4218ce9c01
419
components/telnet/telnet.c
Normal file
419
components/telnet/telnet.c
Normal file
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* Test the telnet functions.
|
||||
*
|
||||
* Perform a test using the telnet functions.
|
||||
* This code exports two new global functions:
|
||||
*
|
||||
* void telnet_listenForClients(void (*callback)(uint8_t *buffer, size_t size))
|
||||
* void telnet_sendData(uint8_t *buffer, size_t size)
|
||||
*
|
||||
* For additional details and documentation see:
|
||||
* * Free book on ESP32 - https://leanpub.com/kolban-ESP32
|
||||
*
|
||||
*
|
||||
* Neil Kolban <kolban1@kolban.com>
|
||||
*
|
||||
* ****************************
|
||||
* Additional portions were taken from
|
||||
* https://github.com/PocketSprite/8bkc-sdk/blob/master/8bkc-components/8bkc-hal/vfs-stdout.c
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h> // Required for libtelnet.h
|
||||
#include <esp_log.h>
|
||||
#include "libtelnet.h"
|
||||
#include "stdbool.h"
|
||||
#include <lwip/def.h>
|
||||
#include <lwip/sockets.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "esp_app_trace.h"
|
||||
#include "telnet.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_attr.h"
|
||||
#include "soc/uart_struct.h"
|
||||
#include "driver/uart.h"
|
||||
#include "config.h"
|
||||
#include "nvs_utilities.h"
|
||||
#include "platform_esp32.h"
|
||||
|
||||
|
||||
#define TELNET_STACK_SIZE 8048
|
||||
|
||||
RingbufHandle_t buf_handle;
|
||||
SemaphoreHandle_t xSemaphore = NULL;
|
||||
static size_t send_chunk=300;
|
||||
static size_t log_buf_size=2000; //32-bit aligned size
|
||||
static bool bIsEnabled=false;
|
||||
const static char tag[] = "telnet";
|
||||
|
||||
int _log_vprintf(const char *fmt, va_list args);
|
||||
void telnet_esp32_listenForClients();
|
||||
int telnet_esp32_vprintf(const char *fmt, va_list va);
|
||||
|
||||
static void telnetTask(void *data);
|
||||
|
||||
|
||||
static int uart_fd=0;
|
||||
|
||||
|
||||
// The global tnHandle ... since we are only processing ONE telnet
|
||||
// client at a time, this can be a global static.
|
||||
static telnet_t *tnHandle;
|
||||
static void handleLogBuffer(int partnerSocket, UBaseType_t bytes);
|
||||
|
||||
struct telnetUserData {
|
||||
int sockfd;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void telnetTask(void *data) {
|
||||
ESP_LOGD(tag, ">> telnetTask");
|
||||
telnet_esp32_listenForClients();
|
||||
ESP_LOGD(tag, "<< telnetTask");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void start_telnet(void * pvParameter){
|
||||
static bool isStarted=false;
|
||||
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
StackType_t *xStack = malloc(TELNET_STACK_SIZE);
|
||||
|
||||
if(!isStarted && bIsEnabled) {
|
||||
xTaskCreateStatic( (TaskFunction_t) &telnetTask, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer);
|
||||
isStarted=true;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t stdout_write(int fd, const void * data, size_t size) {
|
||||
if (xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) {
|
||||
// #1 Write to ringbuffer
|
||||
if (buf_handle == NULL) {
|
||||
printf("%s() ABORT. file handle _log_remote_fp is NULL\n",
|
||||
__FUNCTION__);
|
||||
} else {
|
||||
//Send an item
|
||||
UBaseType_t res = xRingbufferSend(buf_handle, data, size,
|
||||
pdMS_TO_TICKS(100));
|
||||
if (res != pdTRUE) {
|
||||
// flush some entries
|
||||
handleLogBuffer(0, size);
|
||||
res = xRingbufferSend(buf_handle, data, size,
|
||||
pdMS_TO_TICKS(100));
|
||||
if (res != pdTRUE) {
|
||||
|
||||
printf("%s() ABORT. Unable to store log entry in buffer\n",
|
||||
__FUNCTION__);
|
||||
}
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(xSemaphore);
|
||||
} else {
|
||||
// We could not obtain the semaphore and can therefore not access
|
||||
// the shared resource safely.
|
||||
}
|
||||
return write(uart_fd, data, size);
|
||||
}
|
||||
|
||||
static ssize_t stdout_read(int fd, void* data, size_t size) {
|
||||
return read(fd, data, size);
|
||||
}
|
||||
|
||||
static int stdout_open(const char * path, int flags, int mode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stdout_close(int fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stdout_fstat(int fd, struct stat * st) {
|
||||
st->st_mode = S_IFCHR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void kchal_stdout_register() {
|
||||
const esp_vfs_t vfs = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
.write = &stdout_write,
|
||||
.open = &stdout_open,
|
||||
.fstat = &stdout_fstat,
|
||||
.close = &stdout_close,
|
||||
.read = &stdout_read,
|
||||
};
|
||||
uart_fd=open("/dev/uart/0", O_RDWR);
|
||||
ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL));
|
||||
freopen("/dev/pkspstdout", "w", stdout);
|
||||
freopen("/dev/pkspstdout", "w", stderr);
|
||||
//printf("8bkc_hal_stdout_register: Custom stdout/stderr handler installed.\n");
|
||||
}
|
||||
/*********************************
|
||||
* Telnet Support
|
||||
*/
|
||||
|
||||
|
||||
void init_telnet(){
|
||||
|
||||
char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable");
|
||||
|
||||
if (!val || !strcasestr("YX",val) ) {
|
||||
ESP_LOGI(tag,"Telnet support disabled");
|
||||
if(val) free(val);
|
||||
return;
|
||||
}
|
||||
val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block");
|
||||
if(val){
|
||||
send_chunk=atol(val);
|
||||
free(val);
|
||||
send_chunk=send_chunk>0?send_chunk:500;
|
||||
}
|
||||
val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_buffer");
|
||||
if(val){
|
||||
log_buf_size=atol(val);
|
||||
free(val);
|
||||
log_buf_size=log_buf_size>0?log_buf_size:4000;
|
||||
}
|
||||
bIsEnabled=true;
|
||||
|
||||
// Create the semaphore to guard a shared resource.
|
||||
vSemaphoreCreateBinary( xSemaphore );
|
||||
|
||||
// First thing we need to do here is to redirect the output to our telnet handler
|
||||
//Allocate ring buffer data structure and storage area into external RAM
|
||||
StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM);
|
||||
uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM);
|
||||
|
||||
//Create a ring buffer with manually allocated memory
|
||||
buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct);
|
||||
|
||||
if (buf_handle == NULL) {
|
||||
ESP_LOGE(tag,"Failed to create ring buffer for telnet!");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(tag, "***Redirecting log output to telnet");
|
||||
//esp_log_set_vprintf(&_log_vprintf);
|
||||
kchal_stdout_register();
|
||||
}
|
||||
/**
|
||||
* Convert a telnet event type to its string representation.
|
||||
*/
|
||||
static char *eventToString(telnet_event_type_t type) {
|
||||
switch(type) {
|
||||
case TELNET_EV_COMPRESS:
|
||||
return "TELNET_EV_COMPRESS";
|
||||
case TELNET_EV_DATA:
|
||||
return "TELNET_EV_DATA";
|
||||
case TELNET_EV_DO:
|
||||
return "TELNET_EV_DO";
|
||||
case TELNET_EV_DONT:
|
||||
return "TELNET_EV_DONT";
|
||||
case TELNET_EV_ENVIRON:
|
||||
return "TELNET_EV_ENVIRON";
|
||||
case TELNET_EV_ERROR:
|
||||
return "TELNET_EV_ERROR";
|
||||
case TELNET_EV_IAC:
|
||||
return "TELNET_EV_IAC";
|
||||
case TELNET_EV_MSSP:
|
||||
return "TELNET_EV_MSSP";
|
||||
case TELNET_EV_SEND:
|
||||
return "TELNET_EV_SEND";
|
||||
case TELNET_EV_SUBNEGOTIATION:
|
||||
return "TELNET_EV_SUBNEGOTIATION";
|
||||
case TELNET_EV_TTYPE:
|
||||
return "TELNET_EV_TTYPE";
|
||||
case TELNET_EV_WARNING:
|
||||
return "TELNET_EV_WARNING";
|
||||
case TELNET_EV_WILL:
|
||||
return "TELNET_EV_WILL";
|
||||
case TELNET_EV_WONT:
|
||||
return "TELNET_EV_WONT";
|
||||
case TELNET_EV_ZMP:
|
||||
return "TELNET_EV_ZMP";
|
||||
}
|
||||
return "Unknown type";
|
||||
} // eventToString
|
||||
|
||||
|
||||
/**
|
||||
* Send data to the telnet partner.
|
||||
*/
|
||||
void telnet_esp32_sendData(uint8_t *buffer, size_t size) {
|
||||
if (tnHandle != NULL) {
|
||||
telnet_send(tnHandle, (char *)buffer, size);
|
||||
}
|
||||
} // telnet_esp32_sendData
|
||||
|
||||
|
||||
/**
|
||||
* Send a vprintf formatted output to the telnet partner.
|
||||
*/
|
||||
int telnet_esp32_vprintf(const char *fmt, va_list va) {
|
||||
if (tnHandle == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return telnet_vprintf(tnHandle, fmt, va);
|
||||
} // telnet_esp32_vprintf
|
||||
|
||||
/**
|
||||
* Telnet handler.
|
||||
*/
|
||||
void processReceivedData(const char * buffer, size_t size){
|
||||
//ESP_LOGD(tag, "received data, len=%d", event->data.size);
|
||||
|
||||
char * command = malloc(size+1);
|
||||
memcpy(command,buffer,size);
|
||||
command[size]='\0';
|
||||
// todo: implement conditional remote echo
|
||||
//telnet_esp32_sendData((uint8_t *)command, size);
|
||||
if(command[0]!='\r' && command[0]!='\n'){
|
||||
// some telnet clients will send data and crlf in two separate buffers
|
||||
printf(command);
|
||||
printf("\r\n");
|
||||
run_command((char *)command);
|
||||
|
||||
}
|
||||
free(command);
|
||||
|
||||
}
|
||||
static void telnetHandler(
|
||||
telnet_t *thisTelnet,
|
||||
telnet_event_t *event,
|
||||
void *userData) {
|
||||
int rc;
|
||||
struct telnetUserData *telnetUserData = (struct telnetUserData *)userData;
|
||||
switch(event->type) {
|
||||
case TELNET_EV_SEND:
|
||||
rc = send(telnetUserData->sockfd, event->data.buffer, event->data.size, 0);
|
||||
if (rc < 0) {
|
||||
//printf("ERROR: (telnet) send: %d (%s)", errno, strerror(errno));
|
||||
}
|
||||
break;
|
||||
|
||||
case TELNET_EV_DATA:
|
||||
processReceivedData(event->data.buffer, event->data.size);
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("telnet event: %s\n", eventToString(event->type));
|
||||
break;
|
||||
} // End of switch event type
|
||||
} // myTelnetHandler
|
||||
|
||||
|
||||
static void handleLogBuffer(int partnerSocket, UBaseType_t count){
|
||||
//Receive an item from no-split ring buffer
|
||||
size_t item_size;
|
||||
UBaseType_t uxItemsWaiting;
|
||||
UBaseType_t uxBytesToSend=count;
|
||||
|
||||
vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, &uxItemsWaiting);
|
||||
if( partnerSocket ==0 && (uxItemsWaiting*100 / log_buf_size) <75){
|
||||
// We still have some room in the ringbuffer and there's no telnet
|
||||
// connection yet, so bail out for now.
|
||||
//printf("%s() Log buffer used %u of %u bytes used\n", __FUNCTION__, uxItemsWaiting, log_buf_size);
|
||||
return;
|
||||
}
|
||||
|
||||
while(uxBytesToSend>0){
|
||||
char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend);
|
||||
//Check received data
|
||||
|
||||
if (item != NULL) {
|
||||
uxBytesToSend-=item_size;
|
||||
if(partnerSocket!=0)
|
||||
telnet_esp32_sendData((uint8_t *)item, item_size);
|
||||
else{
|
||||
//printf("%s() flushing %u bytes from log buffer\n", __FUNCTION__, item_size);
|
||||
}
|
||||
//Return Item
|
||||
vRingbufferReturnItem(buf_handle, (void *)item);
|
||||
}
|
||||
else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void doTelnet(int partnerSocket) {
|
||||
//ESP_LOGD(tag, ">> doTelnet");
|
||||
static const telnet_telopt_t my_telopts[] = {
|
||||
{ TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DONT },
|
||||
{ TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT },
|
||||
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO },
|
||||
{ TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO },
|
||||
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO },
|
||||
{ TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO },
|
||||
{ TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT },
|
||||
{ -1, 0, 0 }
|
||||
};
|
||||
struct telnetUserData *pTelnetUserData = (struct telnetUserData *)malloc(sizeof(struct telnetUserData));
|
||||
pTelnetUserData->sockfd = partnerSocket;
|
||||
|
||||
|
||||
tnHandle = telnet_init(my_telopts, telnetHandler, 0, pTelnetUserData);
|
||||
|
||||
uint8_t buffer[1024];
|
||||
while(1) {
|
||||
//ESP_LOGD(tag, "waiting for data");
|
||||
ssize_t len = recv(partnerSocket, (char *)buffer, sizeof(buffer), MSG_DONTWAIT);
|
||||
if (len >0 ) {
|
||||
//ESP_LOGD(tag, "received %d bytes", len);
|
||||
telnet_recv(tnHandle, (char *)buffer, len);
|
||||
}
|
||||
else if (errno != EAGAIN && errno !=EWOULDBLOCK ){
|
||||
telnet_free(tnHandle);
|
||||
tnHandle = NULL;
|
||||
free(pTelnetUserData);
|
||||
return;
|
||||
}
|
||||
handleLogBuffer(partnerSocket, send_chunk);
|
||||
|
||||
taskYIELD();
|
||||
}
|
||||
|
||||
} // doTelnet
|
||||
|
||||
/**
|
||||
* Listen for telnet clients and handle them.
|
||||
*/
|
||||
void telnet_esp32_listenForClients() {
|
||||
//ESP_LOGD(tag, ">> telnet_listenForClients");
|
||||
int serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
struct sockaddr_in serverAddr;
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
serverAddr.sin_port = htons(23);
|
||||
|
||||
int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
|
||||
if (rc < 0) {
|
||||
ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
rc = listen(serverSocket, 5);
|
||||
if (rc < 0) {
|
||||
ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
socklen_t len = sizeof(serverAddr);
|
||||
rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len);
|
||||
if (rc < 0 ){
|
||||
ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
int partnerSocket = rc;
|
||||
ESP_LOGD(tag, "We have a new client connection!");
|
||||
doTelnet(partnerSocket);
|
||||
ESP_LOGD(tag, "Telnet connection terminated");
|
||||
}
|
||||
}
|
||||
} // listenForNewClient
|
||||
4
components/telnet/telnet.h
Normal file
4
components/telnet/telnet.h
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
void init_telnet();
|
||||
void start_telnet(void * pvParameter);
|
||||
void telnet_esp32_sendData(uint8_t *buffer, size_t size);
|
||||
@@ -61,7 +61,7 @@ config DEFAULT_AP_BEACON_INTERVAL
|
||||
100ms is the recommended default.
|
||||
config DEFAULT_COMMAND_LINE
|
||||
string "Default command line to execute"
|
||||
default "squeezelite -o I2S -b 500:2000 -d all=info"
|
||||
default "squeezelite -o I2S -b 500:2000 -d all=info -C 30"
|
||||
help
|
||||
This is the command to run when starting the device
|
||||
endmenu
|
||||
|
||||
654
components/wifi-manager/http_server.c
Normal file
654
components/wifi-manager/http_server.c
Normal file
@@ -0,0 +1,654 @@
|
||||
/*
|
||||
Copyright (c) 2017-2019 Tony Pottier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@file http_server.c
|
||||
@author Tony Pottier
|
||||
@brief Defines all functions necessary for the HTTP server to run.
|
||||
|
||||
Contains the freeRTOS task for the HTTP listener and all necessary support
|
||||
function to process requests, decode URLs, serve files, etc. etc.
|
||||
|
||||
@note http_server task cannot run without the wifi_manager task!
|
||||
@see https://idyl.io
|
||||
@see https://github.com/tonyp7/esp32-wifi-manager
|
||||
*/
|
||||
|
||||
#include "http_server.h"
|
||||
#include "cmd_system.h"
|
||||
#include <inttypes.h>
|
||||
#include "squeezelite-ota.h"
|
||||
#include "nvs_utilities.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "cJSON.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "config.h"
|
||||
|
||||
#define HTTP_STACK_SIZE (5*1024)
|
||||
|
||||
/* @brief tag used for ESP serial console messages */
|
||||
static const char TAG[] = "http_server";
|
||||
/* @brief task handle for the http server */
|
||||
static TaskHandle_t task_http_server = NULL;
|
||||
static StaticTask_t task_http_buffer;
|
||||
#if RECOVERY_APPLICATION
|
||||
static StackType_t task_http_stack[HTTP_STACK_SIZE];
|
||||
#else
|
||||
static StackType_t EXT_RAM_ATTR task_http_stack[HTTP_STACK_SIZE];
|
||||
#endif
|
||||
SemaphoreHandle_t http_server_config_mutex = NULL;
|
||||
|
||||
/**
|
||||
* @brief embedded binary data.
|
||||
* @see file "component.mk"
|
||||
* @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data
|
||||
*/
|
||||
extern const uint8_t style_css_start[] asm("_binary_style_css_start");
|
||||
extern const uint8_t style_css_end[] asm("_binary_style_css_end");
|
||||
extern const uint8_t jquery_gz_start[] asm("_binary_jquery_min_js_gz_start");
|
||||
extern const uint8_t jquery_gz_end[] asm("_binary_jquery_min_js_gz_end");
|
||||
extern const uint8_t popper_gz_start[] asm("_binary_popper_min_js_gz_start");
|
||||
extern const uint8_t popper_gz_end[] asm("_binary_popper_min_js_gz_end");
|
||||
extern const uint8_t bootstrap_js_gz_start[] asm("_binary_bootstrap_min_js_gz_start");
|
||||
extern const uint8_t bootstrap_js_gz_end[] asm("_binary_bootstrap_min_js_gz_end");
|
||||
extern const uint8_t bootstrap_css_gz_start[] asm("_binary_bootstrap_min_css_gz_start");
|
||||
extern const uint8_t bootstrap_css_gz_end[] asm("_binary_bootstrap_min_css_gz_end");
|
||||
extern const uint8_t code_js_start[] asm("_binary_code_js_start");
|
||||
extern const uint8_t code_js_end[] asm("_binary_code_js_end");
|
||||
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
|
||||
extern const uint8_t index_html_end[] asm("_binary_index_html_end");
|
||||
|
||||
|
||||
/* const http headers stored in ROM */
|
||||
const static char http_hdr_template[] = "HTTP/1.1 200 OK\nContent-type: %s\nAccept-Ranges: bytes\nContent-Length: %d\nContent-Encoding: %s\nAccess-Control-Allow-Origin: *\n\n";
|
||||
const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
|
||||
const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\nAccess-Control-Allow-Origin: *\n\n";
|
||||
const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccess-Control-Allow-Origin: *\n\n";
|
||||
const static char http_400_hdr[] = "HTTP/1.1 400 Bad Request\nContent-Length: 0\n\n";
|
||||
const static char http_404_hdr[] = "HTTP/1.1 404 Not Found\nContent-Length: 0\n\n";
|
||||
const static char http_503_hdr[] = "HTTP/1.1 503 Service Unavailable\nContent-Length: 0\n\n";
|
||||
const static char http_ok_json_no_cache_hdr[] = "HTTP/1.1 200 OK\nContent-type: application/json\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n";
|
||||
const static char http_redirect_hdr_start[] = "HTTP/1.1 302 Found\nLocation: http://";
|
||||
const static char http_redirect_hdr_end[] = "/\n\n";
|
||||
|
||||
|
||||
void http_server_start() {
|
||||
ESP_LOGD(TAG, "http_server_start ");
|
||||
if(task_http_server == NULL) {
|
||||
task_http_server = xTaskCreateStatic( (TaskFunction_t) &http_server, "http_server", HTTP_STACK_SIZE, NULL,
|
||||
WIFI_MANAGER_TASK_PRIORITY, task_http_stack, &task_http_buffer);
|
||||
}
|
||||
}
|
||||
void http_server(void *pvParameters) {
|
||||
http_server_config_mutex = xSemaphoreCreateMutex();
|
||||
struct netconn *conn, *newconn;
|
||||
err_t err;
|
||||
conn = netconn_new(NETCONN_TCP);
|
||||
netconn_bind(conn, IP_ADDR_ANY, 80);
|
||||
netconn_listen(conn);
|
||||
ESP_LOGI(TAG, "HTTP Server listening on 80/tcp");
|
||||
do {
|
||||
err = netconn_accept(conn, &newconn);
|
||||
if(err == ERR_OK) {
|
||||
http_server_netconn_serve(newconn);
|
||||
netconn_delete(newconn);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Error accepting new connection. Terminating HTTP server");
|
||||
}
|
||||
taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. */
|
||||
} while(err == ERR_OK);
|
||||
|
||||
netconn_close(conn);
|
||||
netconn_delete(conn);
|
||||
vSemaphoreDelete(http_server_config_mutex);
|
||||
http_server_config_mutex = NULL;
|
||||
vTaskDelete( NULL );
|
||||
}
|
||||
|
||||
|
||||
char* http_server_get_header(char *request, char *header_name, int *len) {
|
||||
*len = 0;
|
||||
char *ret = NULL;
|
||||
char *ptr = NULL;
|
||||
|
||||
ptr = strstr(request, header_name);
|
||||
if(ptr) {
|
||||
ret = ptr + strlen(header_name);
|
||||
ptr = ret;
|
||||
while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
|
||||
(*len)++;
|
||||
ptr++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
char* http_server_search_header(char *request, char *header_name, int *len, char ** parm_name, char ** next_position, char * bufEnd) {
|
||||
*len = 0;
|
||||
char *ret = NULL;
|
||||
char *ptr = NULL;
|
||||
int currentLength=0;
|
||||
|
||||
ESP_LOGV(TAG, "searching for header name: [%s]", header_name);
|
||||
ptr = strstr(request, header_name);
|
||||
|
||||
|
||||
if(ptr!=NULL && ptr<bufEnd) {
|
||||
ret = ptr + strlen(header_name);
|
||||
ptr = ret;
|
||||
currentLength=(int)(ptr-request);
|
||||
ESP_LOGV(TAG, "found string at %d", currentLength);
|
||||
|
||||
while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r' && *ptr != ':' && ptr<bufEnd) {
|
||||
ptr++;
|
||||
}
|
||||
if(*ptr==':') {
|
||||
currentLength=(int)(ptr-ret);
|
||||
ESP_LOGV(TAG, "Found parameter name end, length : %d", currentLength);
|
||||
// save the parameter name: the string between header name and ":"
|
||||
*parm_name=malloc(currentLength+1);
|
||||
if(*parm_name==NULL) {
|
||||
ESP_LOGE(TAG, "Unable to allocate memory for new header name");
|
||||
return NULL;
|
||||
}
|
||||
memset(*parm_name, 0x00,currentLength+1);
|
||||
strncpy(*parm_name,ret,currentLength);
|
||||
ESP_LOGV(TAG, "Found parameter name : %s ", *parm_name);
|
||||
ptr++;
|
||||
while (*ptr == ' ' && ptr<bufEnd) {
|
||||
ptr++;
|
||||
}
|
||||
|
||||
}
|
||||
ret=ptr;
|
||||
while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r'&& ptr<bufEnd) {
|
||||
(*len)++;
|
||||
ptr++;
|
||||
}
|
||||
// Terminate value inside its actual buffer so we can treat it as individual string
|
||||
*ptr='\0';
|
||||
currentLength=(int)(ptr-ret);
|
||||
ESP_LOGV(TAG, "Found parameter value end, length : %d, value: %s", currentLength,ret );
|
||||
|
||||
*next_position=++ptr;
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGD(TAG, "No more match for : %s", header_name);
|
||||
return NULL;
|
||||
}
|
||||
void http_server_send_resource_file(struct netconn *conn,const uint8_t * start, const uint8_t * end, char * content_type,char * encoding) {
|
||||
uint16_t len=end - start;
|
||||
size_t buff_length= sizeof(http_hdr_template)+strlen(content_type)+strlen(encoding);
|
||||
char * http_hdr=malloc(buff_length);
|
||||
if( http_hdr == NULL) {
|
||||
ESP_LOGE(TAG, "Cound not allocate %d bytes for headers.",buff_length);
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(http_hdr,0x00,buff_length);
|
||||
snprintf(http_hdr, buff_length-1,http_hdr_template,content_type,len,encoding);
|
||||
netconn_write(conn, http_hdr, strlen(http_hdr), NETCONN_NOCOPY);
|
||||
ESP_LOGD(TAG, "sending response : %s",http_hdr);
|
||||
netconn_write(conn, start, end - start, NETCONN_NOCOPY);
|
||||
free(http_hdr);
|
||||
}
|
||||
}
|
||||
|
||||
err_t http_server_send_config_json(struct netconn *conn) {
|
||||
char * json = config_alloc_get_json(false);
|
||||
if(json!=NULL){
|
||||
ESP_LOGD(TAG, "config json : %s",json );
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, json, strlen(json), NETCONN_NOCOPY);
|
||||
free(json);
|
||||
}
|
||||
else{
|
||||
ESP_LOGD(TAG, "Error retrieving config json string. ");
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void http_server_process_config(struct netconn *conn, char *inbuf) {
|
||||
|
||||
// Here, we are passed a buffer which contains the http request
|
||||
|
||||
// netbuf_data(inbuf, (void**)&buf, &buflen);
|
||||
// err = netconn_recv(conn, &inbuf);
|
||||
// if(err == ERR_OK) {
|
||||
//
|
||||
// /* extract the first line of the request */
|
||||
// char *save_ptr = buf;
|
||||
// char *line = strtok_r(save_ptr, new_line, &save_ptr);
|
||||
// ESP_LOGD(TAG, "Processing line %s",line);
|
||||
ESP_LOGD(TAG, "Processing request buffer: \n%s",inbuf);
|
||||
char *last = NULL;
|
||||
char *ptr = NULL;
|
||||
last = ptr = inbuf;
|
||||
bool bHeaders= true;
|
||||
while(ptr!=NULL && *ptr != '\0') {
|
||||
// Move to the end of the line, or to the end of the buffer
|
||||
if(bHeaders) {
|
||||
while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
|
||||
ptr++;
|
||||
}
|
||||
// terminate the header string
|
||||
if( *(ptr) == '\0' ) {
|
||||
ESP_LOGD(TAG, "End of buffer found");
|
||||
return;
|
||||
}
|
||||
*ptr = '\0';
|
||||
if( *(ptr+1) == '\n' ) {
|
||||
*(ptr+1)='\0';
|
||||
ptr+=2;
|
||||
}
|
||||
if(ptr==last) {
|
||||
ESP_LOGD(TAG, "Processing body. ");
|
||||
break;
|
||||
}
|
||||
if(strlen(last)>0) {
|
||||
ESP_LOGD(TAG, "Found Header Line %s ", last);
|
||||
//Content-Type: application/json
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG, "Found end of headers");
|
||||
bHeaders = false;
|
||||
}
|
||||
last=ptr;
|
||||
}
|
||||
else {
|
||||
//ESP_LOGD(TAG, "Body content: %s", last);
|
||||
//cJSON * json = cJSON_Parse(last);
|
||||
//cJSON_Delete(json);
|
||||
//todo: implement body json parsing
|
||||
// right now, body is coming as compressed, so we need some type of decompression to happen.
|
||||
return;
|
||||
}
|
||||
}
|
||||
return ;
|
||||
|
||||
}
|
||||
|
||||
void dump_net_buffer(void * buf, u16_t buflen) {
|
||||
char * curbuf = malloc(buflen+1);
|
||||
ESP_LOGV(TAG, "netconn buffer, length=%u",buflen);
|
||||
if(curbuf==NULL) {
|
||||
ESP_LOGE(TAG, "Unable to show netconn buffer. Malloc failed");
|
||||
}
|
||||
memset(curbuf,0x0, buflen+1);
|
||||
memcpy(curbuf,buf,buflen);
|
||||
ESP_LOGV(TAG, "netconn buffer content:\n%s",curbuf);
|
||||
free(curbuf);
|
||||
}
|
||||
|
||||
void http_server_netconn_serve(struct netconn *conn) {
|
||||
|
||||
struct netbuf *inbuf;
|
||||
char *buf = NULL;
|
||||
u16_t buflen = 0;
|
||||
err_t err;
|
||||
ip_addr_t remote_add;
|
||||
u16_t port;
|
||||
ESP_LOGV(TAG, "Serving page. Getting device AP address.");
|
||||
const char new_line[2] = "\n";
|
||||
char * ap_ip_address= config_alloc_get_default(NVS_TYPE_STR, "ap_ip_address", DEFAULT_AP_IP, 0);
|
||||
if(ap_ip_address==NULL){
|
||||
ESP_LOGE(TAG, "Unable to retrieve default AP IP Address");
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_close(conn);
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Getting remote device IP address.");
|
||||
netconn_getaddr(conn, &remote_add, &port, 0);
|
||||
char * remote_address = strdup(ip4addr_ntoa(ip_2_ip4(&remote_add)));
|
||||
ESP_LOGD(TAG, "Local Access Point IP address is: %s. Remote device IP address is %s. Receiving request buffer", ap_ip_address, remote_address);
|
||||
|
||||
u16_t bufsize = 0;
|
||||
netconn_set_recvtimeout(conn, 50);
|
||||
while (netconn_recv(conn, &inbuf) == ERR_OK) {
|
||||
do {
|
||||
u8_t *rcvbuf;
|
||||
u16_t rcvlen;
|
||||
netbuf_data(inbuf, (void**)&rcvbuf, &rcvlen);
|
||||
dump_net_buffer(rcvbuf, rcvlen);
|
||||
if (buflen + rcvlen > bufsize) {
|
||||
bufsize += 2048;
|
||||
buf = realloc(buf, bufsize);
|
||||
}
|
||||
memcpy(buf + buflen, rcvbuf, rcvlen);
|
||||
buflen += rcvlen;
|
||||
ESP_LOGI(TAG, "received netbuf of %hu", rcvlen);
|
||||
} while (netbuf_next(inbuf) != -1);
|
||||
netbuf_delete(inbuf);
|
||||
}
|
||||
|
||||
if(buflen) {
|
||||
ESP_LOGV(TAG, "Getting data buffer.");
|
||||
int lenH = 0;
|
||||
/* extract the first line of the request */
|
||||
char *save_ptr = buf;
|
||||
char *line = strtok_r(save_ptr, new_line, &save_ptr);
|
||||
char *temphost = http_server_get_header(save_ptr, "Host: ", &lenH);
|
||||
char * host = malloc(lenH+1);
|
||||
memset(host,0x00,lenH+1);
|
||||
if(lenH>0){
|
||||
strlcpy(host,temphost,lenH+1);
|
||||
}
|
||||
ESP_LOGD(TAG, "http_server_netconn_serve Host: [%s], host: [%s], Processing line [%s]",remote_address,host,line);
|
||||
|
||||
if(line) {
|
||||
|
||||
/* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
|
||||
const char * host_name=NULL;
|
||||
if((err=tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &host_name )) !=ESP_OK) {
|
||||
ESP_LOGE(TAG, "Unable to get host name. Error: %s",esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(TAG,"System host name %s, http requested host: %s.",host_name, host);
|
||||
}
|
||||
|
||||
/* determine if Host is from the STA IP address */
|
||||
wifi_manager_lock_sta_ip_string(portMAX_DELAY);
|
||||
bool access_from_sta_ip = lenH > 0?strcasestr(host, wifi_manager_get_sta_ip_string()):false;
|
||||
wifi_manager_unlock_sta_ip_string();
|
||||
bool access_from_host_name = (host_name!=NULL) && strcasestr(host,host_name);
|
||||
|
||||
if(lenH > 0 && !strcasestr(host, ap_ip_address) && !(access_from_sta_ip || access_from_host_name)) {
|
||||
ESP_LOGI(TAG, "Redirecting host [%s] to AP IP Address : %s",remote_address, ap_ip_address);
|
||||
netconn_write(conn, http_redirect_hdr_start, sizeof(http_redirect_hdr_start) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, ap_ip_address, strlen(ap_ip_address), NETCONN_NOCOPY);
|
||||
netconn_write(conn, http_redirect_hdr_end, sizeof(http_redirect_hdr_end) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
else {
|
||||
//static stuff
|
||||
/* default page */
|
||||
if(strstr(line, "GET / ")) {
|
||||
netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, index_html_start, index_html_end- index_html_start, NETCONN_NOCOPY);
|
||||
}
|
||||
else if(strstr(line, "GET /code.js ")) {
|
||||
netconn_write(conn, http_js_hdr, sizeof(http_js_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, code_js_start, code_js_end - code_js_start, NETCONN_NOCOPY);
|
||||
}
|
||||
else if(strstr(line, "GET /style.css ")) {
|
||||
netconn_write(conn, http_css_hdr, sizeof(http_css_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, style_css_start, style_css_end - style_css_start, NETCONN_NOCOPY);
|
||||
}
|
||||
else if(strstr(line, "GET /jquery.js ")) {
|
||||
http_server_send_resource_file(conn,jquery_gz_start, jquery_gz_end, "text/javascript", "gzip" );
|
||||
}
|
||||
else if(strstr(line, "GET /popper.js ")) {
|
||||
http_server_send_resource_file(conn,popper_gz_start, popper_gz_end, "text/javascript", "gzip" );
|
||||
}
|
||||
else if(strstr(line, "GET /bootstrap.js ")) {
|
||||
http_server_send_resource_file(conn,bootstrap_js_gz_start, bootstrap_js_gz_end, "text/javascript", "gzip" );
|
||||
}
|
||||
else if(strstr(line, "GET /bootstrap.css ")) {
|
||||
http_server_send_resource_file(conn,bootstrap_css_gz_start, bootstrap_css_gz_end, "text/css", "gzip" );
|
||||
}
|
||||
|
||||
//dynamic stuff
|
||||
else if(strstr(line, "GET /scan.json ")) {
|
||||
ESP_LOGI(TAG, "Starting wifi scan");
|
||||
wifi_manager_scan_async();
|
||||
}
|
||||
else if(strstr(line, "GET /ap.json ")) {
|
||||
/* if we can get the mutex, write the last version of the AP list */
|
||||
ESP_LOGI(TAG, "Processing ap.json request");
|
||||
if(wifi_manager_lock_json_buffer(( TickType_t ) 10)) {
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||
char *buff = wifi_manager_alloc_get_ap_list_json();
|
||||
wifi_manager_unlock_json_buffer();
|
||||
if(buff!=NULL){
|
||||
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||
free(buff);
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG, "Error retrieving ap list json string. ");
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
}
|
||||
else {
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
|
||||
}
|
||||
/* request a wifi scan */
|
||||
ESP_LOGI(TAG, "Starting wifi scan");
|
||||
wifi_manager_scan_async();
|
||||
ESP_LOGI(TAG, "Done serving ap.json");
|
||||
}
|
||||
else if(strstr(line, "GET /config.json ")) {
|
||||
ESP_LOGI(TAG, "Serving config.json");
|
||||
ESP_LOGI(TAG, "About to get config from flash");
|
||||
http_server_send_config_json(conn);
|
||||
ESP_LOGD(TAG, "Done serving config.json");
|
||||
}
|
||||
else if(strstr(line, "POST /config.json ")) {
|
||||
ESP_LOGI(TAG, "Serving POST config.json");
|
||||
int lenA=0;
|
||||
char * last_parm=save_ptr;
|
||||
char * next_parm=save_ptr;
|
||||
char * last_parm_name=NULL;
|
||||
bool bErrorFound=false;
|
||||
bool bOTA=false;
|
||||
char * otaURL=NULL;
|
||||
// todo: implement json body parsing
|
||||
//http_server_process_config(conn,save_ptr);
|
||||
|
||||
while(last_parm!=NULL) {
|
||||
// Search will return
|
||||
ESP_LOGD(TAG, "Getting parameters from X-Custom headers");
|
||||
last_parm = http_server_search_header(next_parm, "X-Custom-", &lenA, &last_parm_name,&next_parm,buf+buflen);
|
||||
if(last_parm!=NULL && last_parm_name!=NULL) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: POST config.json, config %s=%s", last_parm_name, last_parm);
|
||||
if(strcmp(last_parm_name, "fwurl")==0) {
|
||||
// we're getting a request to do an OTA from that URL
|
||||
ESP_LOGW(TAG, "Found OTA request!");
|
||||
otaURL=strdup(last_parm);
|
||||
bOTA=true;
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG, "http_server_netconn_serve: POST config.json Storing parameter");
|
||||
if(config_set_value(NVS_TYPE_STR, last_parm_name , last_parm) != ESP_OK){
|
||||
ESP_LOGE(TAG, "Unable to save nvs value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(last_parm_name!=NULL) {
|
||||
free(last_parm_name);
|
||||
last_parm_name=NULL;
|
||||
}
|
||||
}
|
||||
if(bErrorFound) {
|
||||
netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY); //400 invalid request
|
||||
}
|
||||
else {
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
|
||||
if(bOTA) {
|
||||
|
||||
#if RECOVERY_APPLICATION
|
||||
ESP_LOGW(TAG, "Starting process OTA for url %s",otaURL);
|
||||
#else
|
||||
ESP_LOGW(TAG, "Restarting system to process OTA for url %s",otaURL);
|
||||
#endif
|
||||
wifi_manager_reboot_ota(otaURL);
|
||||
free(otaURL);
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Done Serving POST config.json");
|
||||
}
|
||||
else if(strstr(line, "POST /connect.json ")) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: POST /connect.json");
|
||||
bool found = false;
|
||||
int lenS = 0, lenP = 0, lenN = 0;
|
||||
char *ssid = NULL, *password = NULL;
|
||||
ssid = http_server_get_header(save_ptr, "X-Custom-ssid: ", &lenS);
|
||||
password = http_server_get_header(save_ptr, "X-Custom-pwd: ", &lenP);
|
||||
char * new_host_name_b = http_server_get_header(save_ptr, "X-Custom-host_name: ", &lenN);
|
||||
if(lenN > 0){
|
||||
lenN++;
|
||||
char * new_host_name = malloc(lenN);
|
||||
strlcpy(new_host_name, new_host_name_b, lenN);
|
||||
if(config_set_value(NVS_TYPE_STR, "host_name", new_host_name) != ESP_OK){
|
||||
ESP_LOGE(TAG, "Unable to save host name configuration");
|
||||
}
|
||||
free(new_host_name);
|
||||
}
|
||||
|
||||
if(ssid && lenS <= MAX_SSID_SIZE && password && lenP <= MAX_PASSWORD_SIZE) {
|
||||
wifi_config_t* config = wifi_manager_get_wifi_sta_config();
|
||||
memset(config, 0x00, sizeof(wifi_config_t));
|
||||
memcpy(config->sta.ssid, ssid, lenS);
|
||||
memcpy(config->sta.password, password, lenP);
|
||||
ESP_LOGD(TAG, "http_server_netconn_serve: wifi_manager_connect_async() call, with ssid: %s, password: %s", config->sta.ssid, config->sta.password);
|
||||
wifi_manager_connect_async();
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
|
||||
found = true;
|
||||
}
|
||||
else{
|
||||
ESP_LOGE(TAG, "SSID or Password invalid");
|
||||
}
|
||||
|
||||
|
||||
if(!found) {
|
||||
/* bad request the authentification header is not complete/not the correct format */
|
||||
netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
|
||||
ESP_LOGE(TAG, "bad request the authentification header is not complete/not the correct format");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: done serving connect.json");
|
||||
}
|
||||
else if(strstr(line, "DELETE /connect.json ")) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: DELETE /connect.json");
|
||||
/* request a disconnection from wifi and forget about it */
|
||||
wifi_manager_disconnect_async();
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: done serving DELETE /connect.json");
|
||||
}
|
||||
else if(strstr(line, "POST /reboot_ota.json ")) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: POST reboot_ota.json");
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
|
||||
wifi_manager_reboot(OTA);
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: done serving POST reboot_ota.json");
|
||||
}
|
||||
else if(strstr(line, "POST /reboot.json ")) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: POST reboot.json");
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
|
||||
wifi_manager_reboot(RESTART);
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: done serving POST reboot.json");
|
||||
}
|
||||
else if(strstr(line, "POST /recovery.json ")) {
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: POST recovery.json");
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
|
||||
wifi_manager_reboot(RECOVERY);
|
||||
ESP_LOGI(TAG, "http_server_netconn_serve: done serving POST recovery.json");
|
||||
}
|
||||
else if(strstr(line, "GET /status.json ")) {
|
||||
ESP_LOGI(TAG, "Serving status.json");
|
||||
if(wifi_manager_lock_json_buffer(( TickType_t ) 10)) {
|
||||
char *buff = wifi_manager_alloc_get_ip_info_json();
|
||||
wifi_manager_unlock_json_buffer();
|
||||
if(buff) {
|
||||
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||
free(buff);
|
||||
}
|
||||
else {
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||
ESP_LOGE(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
|
||||
}
|
||||
ESP_LOGI(TAG, "Done Serving status.json");
|
||||
}
|
||||
else {
|
||||
netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
|
||||
ESP_LOGE(TAG, "bad request from host: %s, request %s",remote_address, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "URL not found processing for remote host : %s",remote_address);
|
||||
netconn_write(conn, http_404_hdr, sizeof(http_404_hdr) - 1, NETCONN_NOCOPY);
|
||||
}
|
||||
free(host);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
free(ap_ip_address);
|
||||
free(remote_address);
|
||||
netconn_close(conn);
|
||||
/* free the buffer */
|
||||
|
||||
}
|
||||
|
||||
bool http_server_lock_json_object(TickType_t xTicksToWait) {
|
||||
ESP_LOGD(TAG, "Locking config json object");
|
||||
if(http_server_config_mutex) {
|
||||
if( xSemaphoreTake( http_server_config_mutex, xTicksToWait ) == pdTRUE ) {
|
||||
ESP_LOGV(TAG, "config Json object locked!");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG, "Semaphore take failed. Unable to lock config Json object mutex");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG, "Unable to lock config Json object mutex");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void http_server_unlock_json_object() {
|
||||
ESP_LOGD(TAG, "Unlocking json buffer!");
|
||||
xSemaphoreGive( http_server_config_mutex );
|
||||
}
|
||||
|
||||
void strreplace(char *src, char *str, char *rep)
|
||||
{
|
||||
char *p = strstr(src, str);
|
||||
if(p)
|
||||
{
|
||||
int len = strlen(src)+strlen(rep)-strlen(str);
|
||||
char r[len];
|
||||
memset(r, 0, len);
|
||||
if( p >= src ) {
|
||||
strncpy(r, src, p-src);
|
||||
r[p-src]='\0';
|
||||
strncat(r, rep, strlen(rep));
|
||||
strncat(r, p+strlen(str), p+strlen(str)-src+strlen(src));
|
||||
strcpy(src, r);
|
||||
strreplace(p+strlen(rep), str, rep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,6 @@ Contains the freeRTOS task and all necessary support
|
||||
#include "lwip/ip4_addr.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_app_format.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/adc.h"
|
||||
#include "cJSON.h"
|
||||
#include "config.h"
|
||||
#include "trace.h"
|
||||
@@ -65,6 +63,7 @@ Contains the freeRTOS task and all necessary support
|
||||
|
||||
#include "http_server_handlers.h"
|
||||
#include "monitor.h"
|
||||
#include "globdefs.h"
|
||||
|
||||
#ifndef RECOVERY_APPLICATION
|
||||
#define RECOVERY_APPLICATION 0
|
||||
@@ -271,6 +270,10 @@ void wifi_manager_init_wifi(){
|
||||
ESP_LOGD(TAG, "Initializing wifi. Setting WiFi mode to WIFI_MODE_NULL");
|
||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
|
||||
ESP_LOGD(TAG, "Initializing wifi. Starting wifi");
|
||||
if (gpio36_39_used) {
|
||||
ESP_LOGW(TAG, "GPIO 36 or 39 are in use, need to disable WiFi PowerSave!");
|
||||
esp_wifi_set_ps(WIFI_PS_NONE);
|
||||
}
|
||||
ESP_ERROR_CHECK( esp_wifi_start() );
|
||||
taskYIELD();
|
||||
ESP_LOGD(TAG, "Initializing wifi. done");
|
||||
@@ -454,7 +457,7 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
|
||||
cJSON_AddItemToObject(root, "ota_dsc", cJSON_CreateString(ota_get_status()));
|
||||
cJSON_AddNumberToObject(root,"ota_pct", ota_get_pct_complete() );
|
||||
cJSON_AddItemToObject(root, "Jack", cJSON_CreateString(jack_inserted_svc() ? "1" : "0"));
|
||||
cJSON_AddNumberToObject(root,"Voltage", adc1_get_raw(ADC1_CHANNEL_7) / 4095. * (10+174)/10. * 1.1);
|
||||
cJSON_AddNumberToObject(root,"Voltage", battery_value_svc());
|
||||
cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect );
|
||||
cJSON_AddNumberToObject(root,"avg_conn_time", num_disconnect>0?(total_connected_time/num_disconnect):0 );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user