mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 20:17:04 +03:00
Merge remote-tracking branch 'origin/master' into master-cmake
Conflicts: README.md components/config/config.c components/driver_bt/bt_app_sink.c components/raop/raop.c components/services/audio_controls.c main/platform_esp32.h
This commit is contained in:
@@ -1,22 +1,13 @@
|
||||
/*****************************************************************************
|
||||
* alac_wrapper.h: ALAC coder wrapper
|
||||
*
|
||||
* Copyright (C) 2016 Philippe <philippe44@outlook.com>
|
||||
* (c) 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
|
||||
*****************************************************************************/
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __ALAC_WRAPPER_H_
|
||||
#define __ALAC_WRAPPER_H_
|
||||
|
||||
|
||||
Binary file not shown.
711
components/config/config.c
Normal file
711
components/config/config.c
Normal file
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
//#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "config.h"
|
||||
#include "nvs_utilities.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs_utilities.h"
|
||||
#include "cJSON.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
|
||||
#define CONFIG_COMMIT_DELAY 1000
|
||||
#define LOCK_MAX_WAIT 20*CONFIG_COMMIT_DELAY
|
||||
static const char * TAG = "config";
|
||||
static cJSON * nvs_json=NULL;
|
||||
static TimerHandle_t timer;
|
||||
static SemaphoreHandle_t config_mutex = NULL;
|
||||
static EventGroupHandle_t config_group;
|
||||
/* @brief indicate that the ESP32 is currently connected. */
|
||||
static const int CONFIG_NO_COMMIT_PENDING = BIT0;
|
||||
static const int CONFIG_LOAD_BIT = BIT1;
|
||||
|
||||
bool config_lock(TickType_t xTicksToWait);
|
||||
void config_unlock();
|
||||
extern esp_err_t nvs_load_config();
|
||||
void config_raise_change(bool flag);
|
||||
cJSON_bool config_is_entry_changed(cJSON * entry);
|
||||
bool config_set_group_bit(int bit_num,bool flag);
|
||||
cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, void * value);
|
||||
static void vCallbackFunction( TimerHandle_t xTimer );
|
||||
void config_set_entry_changed_flag(cJSON * entry, cJSON_bool flag);
|
||||
#define IMPLEMENT_SET_DEFAULT(t,nt) void config_set_default_## t (const char *key, t value){\
|
||||
void * pval = malloc(sizeof(value));\
|
||||
*((t *) pval) = value;\
|
||||
config_set_default(nt, key,pval,0);\
|
||||
free(pval); }
|
||||
#define IMPLEMENT_GET_NUM(t,nt) esp_err_t config_get_## t (const char *key, t * value){\
|
||||
void * pval = config_alloc_get(nt, key);\
|
||||
if(pval!=NULL){ *value = *(t * )pval; free(pval); return ESP_OK; }\
|
||||
return ESP_FAIL;}
|
||||
#if RECOVERY_APPLICATION==0
|
||||
static void * malloc_fn(size_t sz){
|
||||
|
||||
void * ptr = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM);
|
||||
if(ptr==NULL){
|
||||
ESP_LOGE(TAG,"malloc_fn: unable to allocate memory!");
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
/*
|
||||
static void * free_fn(void * ptr){
|
||||
if(ptr!=NULL){
|
||||
heap_caps_free(ptr);
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG,"free_fn: Cannot free null pointer!");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
void init_cJSON(){
|
||||
static cJSON_Hooks hooks;
|
||||
// initialize cJSON hooks it uses SPIRAM memory
|
||||
// as opposed to IRAM
|
||||
#if RECOVERY_APPLICATION==0
|
||||
// In squeezelite mode, allocate memory from PSRAM. Otherwise allocate from internal RAM
|
||||
// as recovery will lock flash access when erasing FLASH or writing to OTA partition.
|
||||
hooks.malloc_fn=&malloc_fn;
|
||||
//hooks.free_fn=&free_fn;
|
||||
cJSON_InitHooks(&hooks);
|
||||
#endif
|
||||
}
|
||||
void config_init(){
|
||||
ESP_LOGD(TAG, "Creating mutex for Config");
|
||||
config_mutex = xSemaphoreCreateMutex();
|
||||
ESP_LOGD(TAG, "Creating event group");
|
||||
config_group = xEventGroupCreate();
|
||||
ESP_LOGD(TAG, "Loading config from nvs");
|
||||
|
||||
init_cJSON();
|
||||
if(nvs_json !=NULL){
|
||||
cJSON_Delete(nvs_json);
|
||||
}
|
||||
nvs_json = cJSON_CreateObject();
|
||||
|
||||
config_set_group_bit(CONFIG_LOAD_BIT,true);
|
||||
nvs_load_config();
|
||||
config_set_group_bit(CONFIG_LOAD_BIT,false);
|
||||
config_start_timer();
|
||||
}
|
||||
|
||||
void config_start_timer(){
|
||||
ESP_LOGD(TAG, "Starting config timer");
|
||||
timer = xTimerCreate("configTimer", CONFIG_COMMIT_DELAY / portTICK_RATE_MS, pdFALSE, NULL, vCallbackFunction);
|
||||
if( xTimerStart( timer , CONFIG_COMMIT_DELAY/ portTICK_RATE_MS ) != pdPASS ) {
|
||||
ESP_LOGE(TAG, "config commitment timer failed to start.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nvs_type_t config_get_item_type(cJSON * entry){
|
||||
if(entry==NULL){
|
||||
ESP_LOGE(TAG,"null pointer received!");
|
||||
return true;
|
||||
}
|
||||
cJSON * item_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
|
||||
if(item_type ==NULL ) {
|
||||
ESP_LOGE(TAG, "Item type not found! ");
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGD(TAG,"Found item type %f",item_type->valuedouble);
|
||||
return item_type->valuedouble;
|
||||
}
|
||||
|
||||
|
||||
cJSON * config_set_value_safe(nvs_type_t nvs_type, const char *key, void * value){
|
||||
cJSON * entry = cJSON_CreateObject();
|
||||
|
||||
double numvalue = 0;
|
||||
if(entry == NULL) {
|
||||
ESP_LOGE(TAG, "Unable to allocate memory for entry %s",key);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON * existing = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
|
||||
if(existing !=NULL && nvs_type == NVS_TYPE_STR && config_get_item_type(existing) != NVS_TYPE_STR ) {
|
||||
ESP_LOGW(TAG, "Storing numeric value from string");
|
||||
numvalue = atof((char *)value);
|
||||
cJSON_AddNumberToObject(entry,"value", numvalue );
|
||||
nvs_type_t exist_type = config_get_item_type(existing);
|
||||
ESP_LOGW(TAG, "Stored value %f from string %s as type %d",numvalue, (char *)value,exist_type);
|
||||
cJSON_AddNumberToObject(entry,"type", exist_type);
|
||||
}
|
||||
else {
|
||||
cJSON_AddNumberToObject(entry,"type", nvs_type );
|
||||
switch (nvs_type) {
|
||||
case NVS_TYPE_I8:
|
||||
cJSON_AddNumberToObject(entry,"value", *(int8_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_I16:
|
||||
cJSON_AddNumberToObject(entry,"value", *(int16_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_I32:
|
||||
cJSON_AddNumberToObject(entry,"value", *(int32_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_U8:
|
||||
cJSON_AddNumberToObject(entry,"value", *(uint8_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_U16:
|
||||
cJSON_AddNumberToObject(entry,"value", *(uint16_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_U32:
|
||||
cJSON_AddNumberToObject(entry,"value", *(uint32_t*)value );
|
||||
break;
|
||||
case NVS_TYPE_STR:
|
||||
cJSON_AddStringToObject(entry, "value", (char *)value);
|
||||
break;
|
||||
case NVS_TYPE_I64:
|
||||
case NVS_TYPE_U64:
|
||||
default:
|
||||
ESP_LOGE(TAG, "nvs type %u not supported", nvs_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(existing!=NULL ) {
|
||||
ESP_LOGV(TAG, "Changing existing entry [%s].", key);
|
||||
char * exist_str = cJSON_PrintUnformatted(existing);
|
||||
if(exist_str!=NULL){
|
||||
ESP_LOGV(TAG,"Existing entry: %s", exist_str);
|
||||
free(exist_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG,"Failed to print existing entry");
|
||||
}
|
||||
// set commit flag as equal so we can compare
|
||||
cJSON_AddBoolToObject(entry,"chg",config_is_entry_changed(existing));
|
||||
if(!cJSON_Compare(entry,existing,false)){
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGD(TAG,"New config object: \n%s", entry_str );
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG,"Failed to print entry");
|
||||
}
|
||||
ESP_LOGI(TAG, "Setting changed flag config [%s]", key);
|
||||
config_set_entry_changed_flag(entry,true);
|
||||
ESP_LOGI(TAG, "Updating config [%s]", key);
|
||||
cJSON_ReplaceItemInObject(nvs_json,key, entry);
|
||||
entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGD(TAG,"New config: %s", entry_str );
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG,"Failed to print entry");
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG, "Config not changed. ");
|
||||
cJSON_Delete(entry);
|
||||
entry = existing;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This is a new entry.
|
||||
config_set_entry_changed_flag(entry,true);
|
||||
cJSON_AddItemToObject(nvs_json, key, entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
nvs_type_t config_get_entry_type(cJSON * entry){
|
||||
if(entry==NULL){
|
||||
ESP_LOGE(TAG,"null pointer received!");
|
||||
return 0;
|
||||
}
|
||||
cJSON * entry_type = cJSON_GetObjectItemCaseSensitive(entry, "type");
|
||||
if(entry_type ==NULL ) {
|
||||
ESP_LOGE(TAG, "Entry type not found in nvs cache for existing setting.");
|
||||
return 0;
|
||||
}
|
||||
ESP_LOGV(TAG,"Found type %s",type_to_str(entry_type->valuedouble));
|
||||
return entry_type->valuedouble;
|
||||
}
|
||||
void config_set_entry_changed_flag(cJSON * entry, cJSON_bool flag){
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: begin");
|
||||
if(entry==NULL){
|
||||
ESP_LOGE(TAG,"null pointer received!");
|
||||
return;
|
||||
}
|
||||
bool bIsConfigLoading=((xEventGroupGetBits(config_group) & CONFIG_LOAD_BIT)!=0);
|
||||
bool changedFlag=bIsConfigLoading?false:flag;
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: retrieving chg flag from entry");
|
||||
cJSON * changed = cJSON_GetObjectItemCaseSensitive(entry, "chg");
|
||||
if(changed ==NULL ) {
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: chg flag not found. Adding. ");
|
||||
cJSON_AddBoolToObject(entry,"chg",changedFlag);
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: Existing change flag found. ");
|
||||
if(cJSON_IsTrue(changed) && changedFlag){
|
||||
ESP_LOGW(TAG, "Commit flag not changed!");
|
||||
}
|
||||
else{
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: Updating change flag to %s",changedFlag?"TRUE":"FALSE");
|
||||
changed->type = changedFlag?cJSON_True:cJSON_False ;
|
||||
}
|
||||
}
|
||||
|
||||
if(changedFlag) {
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: Calling config_raise_change. ");
|
||||
config_raise_change(true);
|
||||
}
|
||||
ESP_LOGV(TAG, "config_set_entry_changed_flag: done. ");
|
||||
}
|
||||
cJSON_bool config_is_entry_changed(cJSON * entry){
|
||||
if(entry==NULL){
|
||||
ESP_LOGE(TAG,"null pointer received!");
|
||||
return true;
|
||||
}
|
||||
cJSON * changed = cJSON_GetObjectItemCaseSensitive(entry, "chg");
|
||||
if(changed ==NULL ) {
|
||||
ESP_LOGE(TAG, "Change flag not found! ");
|
||||
return true;
|
||||
}
|
||||
return cJSON_IsTrue(changed);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void * config_safe_alloc_get_entry_value(nvs_type_t nvs_type, cJSON * entry){
|
||||
void * value=NULL;
|
||||
if(entry==NULL){
|
||||
ESP_LOGE(TAG,"null pointer received!");
|
||||
}
|
||||
ESP_LOGV(TAG, "getting config value type %s", type_to_str(nvs_type));
|
||||
cJSON * entry_value = cJSON_GetObjectItemCaseSensitive(entry, "value");
|
||||
if(entry_value==NULL ) {
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "Missing config value!. Object: \n%s", entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else{
|
||||
ESP_LOGE(TAG, "Missing config value");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nvs_type_t type = config_get_entry_type(entry);
|
||||
if(nvs_type != type){
|
||||
// requested value type different than the stored type
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "Requested value type %s, found value type %s instead, Object: \n%s", type_to_str(nvs_type), type_to_str(type),entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else{
|
||||
ESP_LOGE(TAG, "Requested value type %s, found value type %s instead", type_to_str(nvs_type), type_to_str(type));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
if (nvs_type == NVS_TYPE_I8) {
|
||||
value=malloc(sizeof(int8_t));
|
||||
*(int8_t *)value = (int8_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_U8) {
|
||||
value=malloc(sizeof(uint8_t));
|
||||
*(uint8_t *)value = (uint8_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_I16) {
|
||||
value=malloc(sizeof(int16_t));
|
||||
*(int16_t *)value = (int16_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_U16) {
|
||||
value=malloc(sizeof(uint16_t));
|
||||
*(uint16_t *)value = (uint16_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_I32) {
|
||||
value=malloc(sizeof(int32_t));
|
||||
*(int32_t *)value = (int32_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_U32) {
|
||||
value=malloc(sizeof(uint32_t));
|
||||
*(uint32_t *)value = (uint32_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_I64) {
|
||||
value=malloc(sizeof(int64_t));
|
||||
*(int64_t *)value = (int64_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_U64) {
|
||||
value=malloc(sizeof(uint64_t));
|
||||
*(uint64_t *)value = (uint64_t)entry_value->valuedouble;
|
||||
} else if (nvs_type == NVS_TYPE_STR) {
|
||||
if(!cJSON_IsString(entry_value)){
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "requested value type string, config type is different. key: %s, value: %s, type %d, Object: \n%s",
|
||||
entry_value->string,
|
||||
entry_value->valuestring,
|
||||
entry_value->type,
|
||||
entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "requested value type string, config type is different. key: %s, value: %s, type %d",
|
||||
entry_value->string,
|
||||
entry_value->valuestring,
|
||||
entry_value->type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
value=(void *)strdup(cJSON_GetStringValue(entry_value));
|
||||
if(value==NULL){
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "strdup failed on value for object \n%s",entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "strdup failed on value");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nvs_type == NVS_TYPE_BLOB) {
|
||||
ESP_LOGE(TAG, "Unsupported type NVS_TYPE_BLOB");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void config_commit_to_nvs(){
|
||||
ESP_LOGI(TAG,"Committing configuration to nvs. Locking config object.");
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs. Locking config object.");
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "config_commit_to_nvs: Unable to lock config for commit ");
|
||||
return ;
|
||||
}
|
||||
if(nvs_json==NULL){
|
||||
ESP_LOGE(TAG, ": cJSON nvs cache object not set.");
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs. Config Locked!");
|
||||
cJSON * entry=nvs_json->child;
|
||||
while(entry!= NULL){
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs processing item %s",entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
|
||||
if(config_is_entry_changed(entry)){
|
||||
ESP_LOGD(TAG, "Committing entry %s value to nvs.",(entry->string==NULL)?"UNKNOWN":entry->string);
|
||||
nvs_type_t type = config_get_entry_type(entry);
|
||||
void * value = config_safe_alloc_get_entry_value(type, entry);
|
||||
if(value!=NULL){
|
||||
esp_err_t err = store_nvs_value(type,entry->string,value);
|
||||
free(value);
|
||||
if(err!=ESP_OK){
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "Error comitting value to nvs for key %s, Object: \n%s",entry->string,entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Error comitting value to nvs for key %s",entry->string);
|
||||
}
|
||||
}
|
||||
else {
|
||||
config_set_entry_changed_flag(entry, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGE(TAG, "Unable to retrieve value. Error comitting value to nvs for key %s, Object: \n%s",entry->string,entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Unable to retrieve value. Error comitting value to nvs for key %s",entry->string);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs. Item already committed. Ignoring.");
|
||||
}
|
||||
taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. */
|
||||
entry = entry->next;
|
||||
}
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs. Resetting the global commit flag.");
|
||||
config_raise_change(false);
|
||||
ESP_LOGV(TAG,"config_commit_to_nvs. Releasing the lock object.");
|
||||
config_unlock();
|
||||
}
|
||||
bool config_has_changes(){
|
||||
return (xEventGroupGetBits(config_group) & CONFIG_NO_COMMIT_PENDING)==0;
|
||||
}
|
||||
|
||||
|
||||
bool wait_for_commit(){
|
||||
bool commit_pending=(xEventGroupGetBits(config_group) & CONFIG_NO_COMMIT_PENDING)==0;
|
||||
while (commit_pending){
|
||||
ESP_LOGW(TAG,"Waiting for config commit ...");
|
||||
commit_pending = (xEventGroupWaitBits(config_group, CONFIG_NO_COMMIT_PENDING,pdFALSE, pdTRUE, (CONFIG_COMMIT_DELAY*2) / portTICK_PERIOD_MS) & CONFIG_NO_COMMIT_PENDING)==0;
|
||||
if(commit_pending){
|
||||
ESP_LOGW(TAG,"Timeout waiting for config commit.");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(TAG,"Config committed!");
|
||||
}
|
||||
}
|
||||
return !commit_pending;
|
||||
}
|
||||
|
||||
bool config_lock(TickType_t xTicksToWait) {
|
||||
ESP_LOGV(TAG, "Locking config json object");
|
||||
if( xSemaphoreTake( config_mutex, xTicksToWait ) == pdTRUE ) {
|
||||
ESP_LOGV(TAG, "config Json object locked!");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Semaphore take failed. Unable to lock config Json object mutex");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void config_unlock() {
|
||||
ESP_LOGV(TAG, "Unlocking json buffer!");
|
||||
xSemaphoreGive( config_mutex );
|
||||
}
|
||||
|
||||
static void vCallbackFunction( TimerHandle_t xTimer ) {
|
||||
static int cnt=0;
|
||||
if(config_has_changes()){
|
||||
ESP_LOGI(TAG, "configuration has some uncommitted entries");
|
||||
config_commit_to_nvs();
|
||||
}
|
||||
else{
|
||||
if(++cnt>=15){
|
||||
ESP_LOGV(TAG,"commit timer: commit flag not set");
|
||||
cnt=0;
|
||||
}
|
||||
}
|
||||
xTimerReset( xTimer, 10 );
|
||||
}
|
||||
void config_raise_change(bool change_found){
|
||||
if(config_set_group_bit(CONFIG_NO_COMMIT_PENDING,!change_found))
|
||||
{
|
||||
ESP_LOGD(TAG,"Config commit set to %s",change_found?"Pending Commit":"Committed");
|
||||
}
|
||||
}
|
||||
bool config_set_group_bit(int bit_num,bool flag){
|
||||
bool result = true;
|
||||
int curFlags=xEventGroupGetBits(config_group);
|
||||
if((curFlags & CONFIG_LOAD_BIT) && bit_num == CONFIG_NO_COMMIT_PENDING ){
|
||||
ESP_LOGD(TAG,"Loading config, ignoring changes");
|
||||
result = false;
|
||||
}
|
||||
if(result){
|
||||
bool curBit=(xEventGroupGetBits(config_group) & bit_num);
|
||||
if(curBit == flag){
|
||||
ESP_LOGV(TAG,"Flag %d already %s", bit_num, flag?"Set":"Cleared");
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
if(result){
|
||||
ESP_LOGV(TAG,"%s Flag %d ", flag?"Setting":"Clearing",bit_num);
|
||||
if(!flag){
|
||||
xEventGroupClearBits(config_group, bit_num);
|
||||
}
|
||||
else {
|
||||
xEventGroupSetBits(config_group, bit_num);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void config_set_default(nvs_type_t type, const char *key, void * default_value, size_t blob_size) {
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Checking if key %s exists in nvs cache for type %s.", key,type_to_str(type));
|
||||
cJSON * entry = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
|
||||
|
||||
if(entry !=NULL){
|
||||
ESP_LOGV(TAG, "Entry found.");
|
||||
}
|
||||
else {
|
||||
// Value was not found
|
||||
ESP_LOGW(TAG, "Adding default value for [%s].", key);
|
||||
entry=config_set_value_safe(type, key, default_value);
|
||||
if(entry == NULL){
|
||||
ESP_LOGE(TAG, "Failed to add value to cache!");
|
||||
}
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGD(TAG, "Value added to default for object: \n%s",entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
}
|
||||
|
||||
config_unlock();
|
||||
|
||||
}
|
||||
|
||||
void config_delete_key(const char *key){
|
||||
nvs_handle nvs;
|
||||
ESP_LOGD(TAG, "Deleting nvs entry for [%s]", key);
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config for delete");
|
||||
return false;
|
||||
}
|
||||
esp_err_t err = nvs_open_from_partition(settings_partition, current_namespace, NVS_READWRITE, &nvs);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_erase_key(nvs, key);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "key [%s] erased from nvs.",key);
|
||||
err = nvs_commit(nvs);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(TAG, "nvs erase committed.");
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Unable to commit nvs erase operation for key [%s]. %s.",key,esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Unable to delete nvs key [%s]. %s. ",key, esp_err_to_name(err));
|
||||
}
|
||||
nvs_close(nvs);
|
||||
}
|
||||
else {
|
||||
ESP_LOGE(TAG, "Error opening nvs: %s. Unable to delete nvs key [%s].",esp_err_to_name(err),key);
|
||||
}
|
||||
char * struc_str = cJSON_PrintUnformatted(nvs_json);
|
||||
if(struc_str!=NULL){
|
||||
ESP_LOGV(TAG, "Structure before delete \n%s", struc_str);
|
||||
free(struc_str);
|
||||
}
|
||||
cJSON * entry = cJSON_DetachItemFromObjectCaseSensitive(nvs_json, key);
|
||||
if(entry !=NULL){
|
||||
ESP_LOGI(TAG, "Removing config key [%s]", entry->string);
|
||||
cJSON_Delete(entry);
|
||||
struc_str = cJSON_PrintUnformatted(nvs_json);
|
||||
if(struc_str!=NULL){
|
||||
ESP_LOGV(TAG, "Structure after delete \n%s", struc_str);
|
||||
free(struc_str);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ESP_LOGW(TAG, "Unable to remove config key [%s]: not found.", key);
|
||||
}
|
||||
config_unlock();
|
||||
}
|
||||
void * config_alloc_get(nvs_type_t nvs_type, const char *key) {
|
||||
return config_alloc_get_default(nvs_type, key, NULL, 0);
|
||||
}
|
||||
void * config_alloc_get_default(nvs_type_t nvs_type, const char *key, void * default_value, size_t blob_size) {
|
||||
|
||||
void * value = NULL;
|
||||
ESP_LOGV(TAG, "Retrieving key %s from nvs cache for type %s.", key,type_to_str(nvs_type));
|
||||
if(nvs_json==NULL){
|
||||
ESP_LOGE(TAG,"configuration not loaded!");
|
||||
return value;
|
||||
}
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config");
|
||||
return value;
|
||||
}
|
||||
ESP_LOGD(TAG,"Getting config entry for key %s",key);
|
||||
cJSON * entry = cJSON_GetObjectItemCaseSensitive(nvs_json, key);
|
||||
if(entry !=NULL){
|
||||
ESP_LOGV(TAG, "Entry found, getting value.");
|
||||
value = config_safe_alloc_get_entry_value(nvs_type, entry);
|
||||
}
|
||||
else if(default_value!=NULL){
|
||||
// Value was not found
|
||||
ESP_LOGW(TAG, "Adding new config value for key [%s]",key);
|
||||
entry=config_set_value_safe(nvs_type, key, default_value);
|
||||
if(entry == NULL){
|
||||
ESP_LOGE(TAG, "Failed to add value to cache");
|
||||
}
|
||||
else {
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGV(TAG, "Value added configuration object for key [%s]: \n%s", entry->string,entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG, "Value added configuration object for key [%s]", entry->string);
|
||||
}
|
||||
value = config_safe_alloc_get_entry_value(nvs_type, entry);
|
||||
}
|
||||
}
|
||||
else{
|
||||
ESP_LOGW(TAG,"Value not found for key %s",key);
|
||||
}
|
||||
config_unlock();
|
||||
return value;
|
||||
}
|
||||
char * config_alloc_get_json(bool bFormatted){
|
||||
char * json_buffer = NULL;
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);
|
||||
return strdup("{\"error\":\"Unable to lock configuration object.\"}");
|
||||
}
|
||||
if(bFormatted){
|
||||
json_buffer= cJSON_Print(nvs_json);
|
||||
}
|
||||
else {
|
||||
json_buffer= cJSON_PrintUnformatted(nvs_json);
|
||||
}
|
||||
config_unlock();
|
||||
return json_buffer;
|
||||
}
|
||||
esp_err_t config_set_value(nvs_type_t nvs_type, const char *key, void * value){
|
||||
esp_err_t result = ESP_OK;
|
||||
if(!config_lock(LOCK_MAX_WAIT/portTICK_PERIOD_MS)){
|
||||
ESP_LOGE(TAG, "Unable to lock config after %d ms",LOCK_MAX_WAIT);
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
cJSON * entry = config_set_value_safe(nvs_type, key, value);
|
||||
if(entry == NULL){
|
||||
result = ESP_FAIL;
|
||||
}
|
||||
else{
|
||||
char * entry_str = cJSON_PrintUnformatted(entry);
|
||||
if(entry_str!=NULL){
|
||||
ESP_LOGV(TAG,"config_set_value result: \n%s",entry_str);
|
||||
free(entry_str);
|
||||
}
|
||||
else {
|
||||
ESP_LOGV(TAG,"config_set_value completed");
|
||||
}
|
||||
}
|
||||
config_unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
IMPLEMENT_SET_DEFAULT(uint8_t,NVS_TYPE_U8);
|
||||
IMPLEMENT_SET_DEFAULT(int8_t,NVS_TYPE_I8);
|
||||
IMPLEMENT_SET_DEFAULT(uint16_t,NVS_TYPE_U16);
|
||||
IMPLEMENT_SET_DEFAULT(int16_t,NVS_TYPE_I16);
|
||||
IMPLEMENT_SET_DEFAULT(uint32_t,NVS_TYPE_U32);
|
||||
IMPLEMENT_SET_DEFAULT(int32_t,NVS_TYPE_I32);
|
||||
|
||||
IMPLEMENT_GET_NUM(uint8_t,NVS_TYPE_U8);
|
||||
IMPLEMENT_GET_NUM(int8_t,NVS_TYPE_I8);
|
||||
IMPLEMENT_GET_NUM(uint16_t,NVS_TYPE_U16);
|
||||
IMPLEMENT_GET_NUM(int16_t,NVS_TYPE_I16);
|
||||
IMPLEMENT_GET_NUM(uint32_t,NVS_TYPE_U32);
|
||||
IMPLEMENT_GET_NUM(int32_t,NVS_TYPE_I32);
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GDS_DRAW_H_
|
||||
#define _GDS_DRAW_H_
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "math.h"
|
||||
#include "esp32/rom/tjpgd.h"
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GDS_PRIVATE_H_
|
||||
#define _GDS_PRIVATE_H_
|
||||
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
/*
|
||||
* (c) 2004,2006 Richard Titmuss for SlimProtoLib
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
* (c) 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 software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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 <string.h>
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
* (c) 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 software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@@ -21,6 +21,7 @@ static int I2CWait;
|
||||
|
||||
static const int GDS_I2C_COMMAND_MODE = 0x80;
|
||||
static const int GDS_I2C_DATA_MODE = 0x40;
|
||||
static const int i2c_timeout_value=2000;
|
||||
|
||||
static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength );
|
||||
static bool I2CDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
|
||||
@@ -49,6 +50,9 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
|
||||
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_param_config( I2CPortNumber, &Config ), return false );
|
||||
ESP_ERROR_CHECK_NONFATAL( i2c_driver_install( I2CPortNumber, Config.mode, 0, 0, 0 ), return false );
|
||||
printf("Setting timeout value to %d",i2c_timeout_value);
|
||||
i2c_set_timeout(I2CPortNumber, (I2C_APB_CLK_FREQ /(2500000))* i2c_timeout_value);
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
/*
|
||||
* (c) 2004,2006 Richard Titmuss for SlimProtoLib
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
/*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -71,8 +71,7 @@ static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap;
|
||||
static _lock_t s_volume_lock;
|
||||
static uint8_t s_volume = 0;
|
||||
static bool s_volume_notify;
|
||||
static bool s_playing = false;
|
||||
static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_ACTIVATED } s_audio = AUDIO_IDLE;
|
||||
static enum { AUDIO_IDLE, AUDIO_CONNECTED, AUDIO_PLAYING } s_audio = AUDIO_IDLE;
|
||||
|
||||
static int s_sample_rate;
|
||||
static int tl;
|
||||
@@ -88,41 +87,49 @@ static EXT_RAM_ATTR struct {
|
||||
bool updated;
|
||||
} s_metadata;
|
||||
|
||||
static void bt_volume_up(void) {
|
||||
static void bt_volume_up(bool pressed) {
|
||||
if (!pressed) return;
|
||||
// volume UP/DOWN buttons are not supported by iPhone/Android
|
||||
volume_set_by_local_host(s_volume < 127-3 ? s_volume + 3 : 127);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
|
||||
ESP_LOGI(BT_AV_TAG, "BT volume up %u", s_volume);
|
||||
}
|
||||
|
||||
static void bt_volume_down(void) {
|
||||
static void bt_volume_down(bool pressed) {
|
||||
if (!pressed) return;
|
||||
// volume UP/DOWN buttons are not supported by iPhone/Android
|
||||
volume_set_by_local_host(s_volume > 3 ? s_volume - 3 : 0);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_VOLUME, s_volume);
|
||||
}
|
||||
|
||||
static void bt_toggle(void) {
|
||||
if (s_playing) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
static void bt_toggle(bool pressed) {
|
||||
if (!pressed) return;
|
||||
if (s_audio == AUDIO_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
else esp_avrc_ct_send_passthrough_cmd(tl++, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
static void bt_play(void) {
|
||||
static void bt_play(bool pressed) {
|
||||
if (!pressed) return;
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PLAY, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
static void bt_pause(void) {
|
||||
static void bt_pause(bool pressed) {
|
||||
if (!pressed) return;
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_PAUSE, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
static void bt_stop(void) {
|
||||
static void bt_stop(bool pressed) {
|
||||
if (!pressed) return;
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
static void bt_prev(void) {
|
||||
static void bt_prev(bool pressed) {
|
||||
if (!pressed) return;
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_BACKWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
static void bt_next(void) {
|
||||
static void bt_next(bool pressed) {
|
||||
if (!pressed) return;
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_FORWARD, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
|
||||
@@ -139,14 +146,14 @@ const static actrls_t controls = {
|
||||
/* disconnection */
|
||||
void bt_disconnect(void) {
|
||||
displayer_control(DISPLAYER_SHUTDOWN);
|
||||
if (s_playing) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
if (s_audio == AUDIO_PLAYING) esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
actrls_unset();
|
||||
ESP_LOGI(BT_AV_TAG, "forced disconnection");
|
||||
ESP_LOGI(BT_AV_TAG, "forced disconnection %d", s_audio);
|
||||
}
|
||||
|
||||
/* update metadata if any */
|
||||
void update_metadata(bool force) {
|
||||
if ((s_metadata.updated || force) && s_audio == AUDIO_ACTIVATED) {
|
||||
if ((s_metadata.updated || force) && s_audio == AUDIO_PLAYING) {
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, -1, s_metadata.duration);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_METADATA, s_metadata.artist, s_metadata.album, s_metadata.title);
|
||||
s_metadata.updated = false;
|
||||
@@ -290,18 +297,17 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
|
||||
|
||||
// verify that we can take control
|
||||
if ((*bt_app_a2d_cmd_cb)(BT_SINK_AUDIO_STARTED, s_sample_rate)) {
|
||||
// resynchronize events as¨PLAY might be sent before STARTED ...
|
||||
s_audio = AUDIO_ACTIVATED;
|
||||
|
||||
// send PLAY there, in case it was sent before AUDIO_STATE
|
||||
if (s_playing) (*bt_app_a2d_cmd_cb)(BT_SINK_PLAY);
|
||||
// if PLAY is sent before AUDIO_STARTED, generate the event here
|
||||
s_audio = AUDIO_PLAYING;
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY);
|
||||
|
||||
// force metadata update
|
||||
update_metadata(true);
|
||||
|
||||
actrls_set(controls, NULL);
|
||||
} else if (s_playing) {
|
||||
// if decoder is busy but BT is playing, stop it (would be better to not ACK this command, but don't know how)
|
||||
actrls_set(controls, false, NULL, actrls_ir_action);
|
||||
} else {
|
||||
// if decoder is busy, stop it (would be better to not ACK this command, but don't know how)
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
}
|
||||
} else if (ESP_A2D_AUDIO_STATE_STOPPED == a2d->audio_stat.state ||
|
||||
@@ -382,19 +388,33 @@ void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_param
|
||||
break;
|
||||
case ESP_AVRC_RN_PLAY_STATUS_CHANGE:
|
||||
ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback);
|
||||
// re-synchronize events
|
||||
s_playing = (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING);
|
||||
if (event_parameter->playback == ESP_AVRC_PLAYBACK_PLAYING && s_audio != AUDIO_IDLE) {
|
||||
// if decoder is busy then stop (would be better to not ACK this command, but don't know how)
|
||||
if (s_audio == AUDIO_CONNECTED || !(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY)) {
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
} else {
|
||||
update_metadata(false);
|
||||
if (s_audio != AUDIO_IDLE) {
|
||||
switch (event_parameter->playback) {
|
||||
case ESP_AVRC_PLAYBACK_PLAYING:
|
||||
// if decoder is busy then stop (would be better to not ACK this command, but don't know how)
|
||||
if (s_audio != AUDIO_PLAYING && !(*bt_app_a2d_cmd_cb)(BT_SINK_PLAY)) {
|
||||
ESP_LOGW(BT_AV_TAG, "Player busy with another controller");
|
||||
esp_avrc_ct_send_passthrough_cmd(tl++ & 0x0f, ESP_AVRC_PT_CMD_STOP, ESP_AVRC_PT_CMD_STATE_PRESSED);
|
||||
} else {
|
||||
s_audio = AUDIO_PLAYING;
|
||||
update_metadata(false);
|
||||
}
|
||||
break;
|
||||
case ESP_AVRC_PLAYBACK_PAUSED:
|
||||
s_audio = AUDIO_CONNECTED;
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PAUSE);
|
||||
break;
|
||||
case ESP_AVRC_PLAYBACK_STOPPED:
|
||||
s_audio = AUDIO_CONNECTED;
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, -1);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_STOP);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(BT_AV_TAG, "Un-handled event");
|
||||
break;
|
||||
}
|
||||
} else if (event_parameter->playback == ESP_AVRC_PLAYBACK_PAUSED) (*bt_app_a2d_cmd_cb)(BT_SINK_PAUSE);
|
||||
else if (event_parameter->playback == ESP_AVRC_PLAYBACK_STOPPED) {
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_PROGRESS, 0, -1);
|
||||
(*bt_app_a2d_cmd_cb)(BT_SINK_STOP);
|
||||
} else {
|
||||
ESP_LOGW(BT_AV_TAG, "Not yet in BT connected mode: 0x%x", event_parameter->playback);
|
||||
}
|
||||
bt_av_playback_changed();
|
||||
break;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "platform_config.h"
|
||||
#include "trace.h"
|
||||
|
||||
static const char * TAG = "platform";
|
||||
static const char * TAG = "bt_app_source";
|
||||
|
||||
extern int32_t output_bt_data(uint8_t *data, int32_t len);
|
||||
extern void output_bt_tick(void);
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) Philippe, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
/*
|
||||
*
|
||||
* (c) Philippe 2019, philippe_44@outlook.com
|
||||
* (c) Philippe 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -957,4 +947,5 @@ static void on_dmap_string(void *ctx, const char *code, const char *name, const
|
||||
return q - (unsigned char *) data;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/*----------------------------------------------------------------------------*/
|
||||
static void on_dmap_string(void *ctx, const char *code, const char *name, const char *buf, size_t len) {
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
/*
|
||||
* AirCast: Chromecast to AirPlay
|
||||
*
|
||||
* (c) Philippe 2016-2017, philippe_44@outlook.com
|
||||
* (c) Philippe 2016-2017, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -32,42 +32,50 @@ static log_level *loglevel = &raop_loglevel;
|
||||
static struct raop_ctx_s *raop;
|
||||
static raop_cmd_vcb_t cmd_handler_chain;
|
||||
|
||||
static void raop_volume_up(void) {
|
||||
static void raop_volume_up(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_VOLUME_UP, NULL);
|
||||
LOG_INFO("AirPlay volume up");
|
||||
}
|
||||
|
||||
static void raop_volume_down(void) {
|
||||
static void raop_volume_down(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_VOLUME_DOWN, NULL);
|
||||
LOG_INFO("AirPlay volume down");
|
||||
}
|
||||
|
||||
static void raop_toggle(void) {
|
||||
static void raop_toggle(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_TOGGLE, NULL);
|
||||
LOG_INFO("AirPlay play/pause");
|
||||
}
|
||||
|
||||
static void raop_pause(void) {
|
||||
static void raop_pause(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_PAUSE, NULL);
|
||||
LOG_INFO("AirPlay pause");
|
||||
}
|
||||
|
||||
static void raop_play(void) {
|
||||
static void raop_play(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_PLAY, NULL);
|
||||
LOG_INFO("AirPlay play");
|
||||
}
|
||||
|
||||
static void raop_stop(void) {
|
||||
static void raop_stop(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_STOP, NULL);
|
||||
LOG_INFO("AirPlay stop");
|
||||
}
|
||||
|
||||
static void raop_prev(void) {
|
||||
static void raop_prev(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_PREV, NULL);
|
||||
LOG_INFO("AirPlay previous");
|
||||
}
|
||||
|
||||
static void raop_next(void) {
|
||||
static void raop_next(bool pressed) {
|
||||
if (!pressed) return;
|
||||
raop_cmd(raop, RAOP_NEXT, NULL);
|
||||
LOG_INFO("AirPlay next");
|
||||
}
|
||||
@@ -99,7 +107,7 @@ static bool cmd_handler(raop_event_t event, ...) {
|
||||
// now handle events for display
|
||||
switch(event) {
|
||||
case RAOP_SETUP:
|
||||
actrls_set(controls, NULL);
|
||||
actrls_set(controls, false, NULL, actrls_ir_action);
|
||||
displayer_control(DISPLAYER_ACTIVATE, "AIRPLAY");
|
||||
break;
|
||||
case RAOP_PLAY:
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
/*
|
||||
* AirConnect: Chromecast & UPnP to AirPlay
|
||||
* AirConnect: Chromecast & UPnP to AirPlay
|
||||
*
|
||||
* (c) Philippe 2016-2017, philippe_44@outlook.com
|
||||
* (c) Philippe 2016-2017, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
/*
|
||||
* Misc utilities
|
||||
*
|
||||
* (c) Adrian Smith 2012-2014, triode1@btinternet.com
|
||||
* (c) Philippe 2016-2017, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
@@ -26,6 +16,7 @@
|
||||
#include "cJSON.h"
|
||||
#include "buttons.h"
|
||||
#include "platform_config.h"
|
||||
#include "accessors.h"
|
||||
#include "audio_controls.h"
|
||||
|
||||
typedef esp_err_t (actrls_config_map_handler) (const cJSON * member, actrls_config_t *cur_config,uint32_t offset);
|
||||
@@ -42,6 +33,9 @@ static esp_err_t actrls_process_type (const cJSON * member, actrls_config_t *cur
|
||||
static esp_err_t actrls_process_bool (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
|
||||
static esp_err_t actrls_process_action (const cJSON * member, actrls_config_t *cur_config, uint32_t offset);
|
||||
|
||||
static esp_err_t actrls_init_json(const char *profile_name, bool create);
|
||||
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press);
|
||||
|
||||
static const actrls_config_map_t actrls_config_map[] =
|
||||
{
|
||||
{"gpio", offsetof(actrls_config_t,gpio), actrls_process_int},
|
||||
@@ -70,11 +64,88 @@ static actrls_config_t *json_config;
|
||||
cJSON * control_profiles = NULL;
|
||||
static actrls_t default_controls, current_controls;
|
||||
static actrls_hook_t *default_hook, *current_hook;
|
||||
static bool default_raw_controls, current_raw_controls;
|
||||
static actrls_ir_handler_t *default_ir_handler, *current_ir_handler;
|
||||
|
||||
static struct {
|
||||
bool long_state;
|
||||
bool volume_lock;
|
||||
} rotary;
|
||||
|
||||
static const struct ir_action_map_s{
|
||||
uint32_t code;
|
||||
actrls_action_e action;
|
||||
} ir_action_map[] = {
|
||||
{0x7689b04f, BCTRLS_DOWN}, {0x7689906f, BCTRLS_LEFT}, {0x7689d02f, BCTRLS_RIGHT}, {0x7689e01f, BCTRLS_UP},
|
||||
{0x768900ff, ACTRLS_VOLDOWN}, {0x7689807f, ACTRLS_VOLUP},
|
||||
{0x7689c03f, ACTRLS_PREV}, {0x7689a05f, ACTRLS_NEXT},
|
||||
{0x768920df, ACTRLS_PAUSE}, {0x768910ef, ACTRLS_PLAY},
|
||||
{0x00, 0x00},
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
* This function can be called to map IR codes to default actions
|
||||
*/
|
||||
bool actrls_ir_action(uint16_t addr, uint16_t cmd) {
|
||||
uint32_t code = (addr << 16) | cmd;
|
||||
struct ir_action_map_s const *map = ir_action_map;
|
||||
|
||||
while (map->code && map->code != code) map++;
|
||||
|
||||
if (map->code && current_controls[map->action]) {
|
||||
current_controls[map->action](true);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void ir_handler(uint16_t addr, uint16_t cmd) {
|
||||
ESP_LOGD(TAG, "recaived IR %04hx:%04hx", addr, cmd);
|
||||
if (current_ir_handler) current_ir_handler(addr, cmd);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static void set_ir_gpio(int gpio, char *value) {
|
||||
if (!strcasecmp(value, "ir") ) {
|
||||
create_infrared(gpio, ir_handler);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t actrls_init(const char *profile_name) {
|
||||
esp_err_t err = ESP_OK;
|
||||
char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
|
||||
|
||||
if (config && *config) {
|
||||
char *p;
|
||||
int A = -1, B = -1, SW = -1, longpress = 0;
|
||||
|
||||
// parse config
|
||||
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
|
||||
if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
|
||||
|
||||
// create rotary (no handling of long press)
|
||||
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
// set infrared GPIO if any
|
||||
parse_set_GPIO(set_ir_gpio);
|
||||
|
||||
if (!err) return actrls_init_json(profile_name, true);
|
||||
else return err;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -82,6 +153,13 @@ static void control_handler(void *client, button_event_e event, button_press_e p
|
||||
actrls_config_t *key = (actrls_config_t*) client;
|
||||
actrls_action_detail_t action_detail;
|
||||
|
||||
// in raw mode, we just do normal action press *and* release, there is no longpress nor shift
|
||||
if (current_raw_controls) {
|
||||
ESP_LOGD(TAG, "calling action %u in raw mode", key->normal[0].action);
|
||||
if (current_controls[key->normal[0].action]) (*current_controls[key->normal[0].action])(event == BUTTON_PRESSED);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(press) {
|
||||
case BUTTON_NORMAL:
|
||||
if (long_press) action_detail = key->longpress[event == BUTTON_PRESSED ? 0 : 1];
|
||||
@@ -96,7 +174,7 @@ static void control_handler(void *client, button_event_e event, button_press_e p
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%u", key->gpio, press, long_press, event,action_detail.action);
|
||||
ESP_LOGD(TAG, "control gpio:%u press:%u long:%u event:%u action:%u", key->gpio, press, long_press, event, action_detail.action);
|
||||
|
||||
// stop here if control hook served the request
|
||||
if (current_hook && (*current_hook)(key->gpio, action_detail.action, event, press, long_press)) return;
|
||||
@@ -123,7 +201,7 @@ static void control_handler(void *client, button_event_e event, button_press_e p
|
||||
}
|
||||
} else if (action_detail.action != ACTRLS_NONE) {
|
||||
ESP_LOGD(TAG, "calling action %u", action_detail.action);
|
||||
if (current_controls[action_detail.action]) (*current_controls[action_detail.action])();
|
||||
if (current_controls[action_detail.action]) (*current_controls[action_detail.action])(event == BUTTON_PRESSED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +210,15 @@ static void control_handler(void *client, button_event_e event, button_press_e p
|
||||
*/
|
||||
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press) {
|
||||
actrls_action_e action = ACTRLS_NONE;
|
||||
bool pressed = true;
|
||||
|
||||
// in raw mode, we just pass rotary events
|
||||
if (current_raw_controls) {
|
||||
if (event == ROTARY_LEFT) (*current_controls[KNOB_LEFT])(true);
|
||||
else if (event == ROTARY_RIGHT) (*current_controls[KNOB_RIGHT])(true);
|
||||
else (*current_controls[KNOB_PUSH])(event == ROTARY_PRESSED);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(event) {
|
||||
case ROTARY_LEFT:
|
||||
@@ -153,17 +240,7 @@ static void control_rotary_handler(void *client, rotary_event_e event, bool long
|
||||
break;
|
||||
}
|
||||
|
||||
if (action != ACTRLS_NONE) (*current_controls[action])();
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t actrls_init(int n, const actrls_config_t *config) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
button_create((void*) (config + i), config[i].gpio, config[i].type, config[i].pull, config[i].debounce, control_handler, config[i].long_press, config[i].shifter_gpio);
|
||||
}
|
||||
return ESP_OK;
|
||||
if (action != ACTRLS_NONE) (*current_controls[action])(pressed);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -370,30 +447,13 @@ static void actrls_defaults(actrls_config_t *config) {
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t actrls_init_json(const char *profile_name, bool create) {
|
||||
static esp_err_t actrls_init_json(const char *profile_name, bool create) {
|
||||
esp_err_t err = ESP_OK;
|
||||
actrls_config_t *cur_config = NULL;
|
||||
actrls_config_t *config_root = NULL;
|
||||
char *config;
|
||||
const cJSON *button;
|
||||
|
||||
char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
|
||||
if (config && *config) {
|
||||
char *p;
|
||||
int A = -1, B = -1, SW = -1, longpress = 0;
|
||||
|
||||
// parse config
|
||||
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "volume")) != NULL) rotary.volume_lock = true;
|
||||
if ((p = strcasestr(config, "longpress")) != NULL) longpress = 1000;
|
||||
|
||||
// create rotary (no handling of long press)
|
||||
err = create_rotary(NULL, A, B, SW, longpress, control_rotary_handler) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
if (config) free(config);
|
||||
|
||||
if (!profile_name || !*profile_name) return ESP_OK;
|
||||
|
||||
config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
|
||||
@@ -425,8 +485,9 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
|
||||
esp_err_t loc_err = actrls_process_button(button, cur_config);
|
||||
err = (err == ESP_OK) ? loc_err : err;
|
||||
if (loc_err == ESP_OK) {
|
||||
if (create) button_create((void*) cur_config, cur_config->gpio,cur_config->type, cur_config->pull,cur_config->debounce,
|
||||
control_handler, cur_config->long_press, cur_config->shifter_gpio);
|
||||
if (create) button_create((void*) cur_config, cur_config->gpio,cur_config->type,
|
||||
cur_config->pull,cur_config->debounce, control_handler,
|
||||
cur_config->long_press, cur_config->shifter_gpio);
|
||||
} else {
|
||||
ESP_LOGE(TAG,"Error parsing button structure. Button will not be registered.");
|
||||
}
|
||||
@@ -448,18 +509,22 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void actrls_set_default(const actrls_t controls, actrls_hook_t *hook) {
|
||||
void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
|
||||
memcpy(default_controls, controls, sizeof(actrls_t));
|
||||
memcpy(current_controls, default_controls, sizeof(actrls_t));
|
||||
default_hook = current_hook = hook;
|
||||
default_raw_controls = current_raw_controls = raw_controls;
|
||||
default_ir_handler = current_ir_handler = ir_handler;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void actrls_set(const actrls_t controls, actrls_hook_t *hook) {
|
||||
void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler) {
|
||||
memcpy(current_controls, controls, sizeof(actrls_t));
|
||||
current_hook = hook;
|
||||
current_raw_controls = raw_controls;
|
||||
current_ir_handler = ir_handler;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -468,4 +533,6 @@ void actrls_set(const actrls_t controls, actrls_hook_t *hook) {
|
||||
void actrls_unset(void) {
|
||||
memcpy(current_controls, default_controls, sizeof(actrls_t));
|
||||
current_hook = default_hook;
|
||||
current_raw_controls = default_raw_controls;
|
||||
current_ir_handler = default_ir_handler;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
/*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -28,9 +18,10 @@ typedef enum { ACTRLS_NONE = -1, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, A
|
||||
ACTRLS_REMAP, ACTRLS_MAX
|
||||
} actrls_action_e;
|
||||
|
||||
typedef void (*actrls_handler)(void);
|
||||
typedef void (*actrls_handler)(bool pressed);
|
||||
typedef actrls_handler actrls_t[ACTRLS_MAX - ACTRLS_NONE - 1];
|
||||
typedef bool actrls_hook_t(int gpio, actrls_action_e action, button_event_e event, button_press_e press, bool long_press);
|
||||
typedef bool actrls_ir_handler_t(uint16_t addr, uint16_t cmd);
|
||||
|
||||
// BEWARE any change to struct below must be mapped to actrls_config_map
|
||||
typedef struct {
|
||||
@@ -47,14 +38,16 @@ typedef struct actrl_config_s {
|
||||
actrls_action_detail_t normal[2], longpress[2], shifted[2], longshifted[2]; // [0] keypressed, [1] keyreleased
|
||||
} actrls_config_t;
|
||||
|
||||
esp_err_t actrls_init(int n, const actrls_config_t *config);
|
||||
esp_err_t actrls_init_json(const char *profile_name, bool create);
|
||||
esp_err_t actrls_init(const char *profile_name);
|
||||
|
||||
/*
|
||||
Set hook function to non-null to be set your own direct managemet function,
|
||||
which should return true if it managed the control request, false if the
|
||||
normal handling should be done
|
||||
The add_release boolean forces a release event to be sent if a press action has been
|
||||
set, whether a release action has been set or not
|
||||
*/
|
||||
void actrls_set_default(const actrls_t controls, actrls_hook_t *hook);
|
||||
void actrls_set(const actrls_t controls, actrls_hook_t *hook);
|
||||
void actrls_set_default(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
|
||||
void actrls_set(const actrls_t controls, bool raw_controls, actrls_hook_t *hook, actrls_ir_handler_t *ir_handler);
|
||||
void actrls_unset(void);
|
||||
bool actrls_ir_action(uint16_t addr, uint16_t code);
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -30,6 +20,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "buttons.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "globdefs.h"
|
||||
@@ -66,11 +57,29 @@ static struct {
|
||||
rotary_handler handler;
|
||||
} rotary;
|
||||
|
||||
static struct {
|
||||
RingbufHandle_t rb;
|
||||
infrared_handler handler;
|
||||
} infrared;
|
||||
|
||||
static xQueueHandle button_evt_queue;
|
||||
static QueueSetHandle_t button_queue_set;
|
||||
static QueueSetHandle_t common_queue_set;
|
||||
|
||||
static void buttons_task(void* arg);
|
||||
|
||||
/****************************************************************************************
|
||||
* Start task needed by button,s rotaty and infrared
|
||||
*/
|
||||
static void common_task_init(void) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
|
||||
if (!common_queue_set) {
|
||||
common_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
|
||||
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* GPIO low-level handler
|
||||
*/
|
||||
@@ -117,8 +126,8 @@ static void buttons_task(void* arg) {
|
||||
while (1) {
|
||||
QueueSetMemberHandle_t xActivatedMember;
|
||||
|
||||
// wait on button and rotary queues
|
||||
if ((xActivatedMember = xQueueSelectFromSet( button_queue_set, portMAX_DELAY )) == NULL) continue;
|
||||
// wait on button, rotary and infrared queues
|
||||
if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
|
||||
|
||||
if (xActivatedMember == button_evt_queue) {
|
||||
struct button_s button;
|
||||
@@ -160,7 +169,7 @@ static void buttons_task(void* arg) {
|
||||
// button is a copy, so need to go to real context
|
||||
button.self->shifting = false;
|
||||
}
|
||||
} else {
|
||||
} else if (xActivatedMember == rotary.queue) {
|
||||
rotary_encoder_event_t event = { 0 };
|
||||
|
||||
// received a rotary event
|
||||
@@ -171,6 +180,9 @@ static void buttons_task(void* arg) {
|
||||
|
||||
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
|
||||
ROTARY_RIGHT : ROTARY_LEFT, false);
|
||||
} else {
|
||||
// this is IR
|
||||
infrared_receive(infrared.rb, infrared.handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,18 +198,14 @@ void dummy_handler(void *id, button_event_e event, button_press_e press) {
|
||||
* Create buttons
|
||||
*/
|
||||
void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[BUTTON_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
|
||||
if (n_buttons >= MAX_BUTTONS) return;
|
||||
|
||||
ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %d", gpio, type, pull, long_press, shifter_gpio);
|
||||
|
||||
if (!n_buttons) {
|
||||
button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
|
||||
if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
|
||||
xQueueAddToSet( button_evt_queue, button_queue_set );
|
||||
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
|
||||
common_task_init();
|
||||
xQueueAddToSet( button_evt_queue, common_queue_set );
|
||||
}
|
||||
|
||||
// just in case this structure is allocated in a future release
|
||||
@@ -350,8 +358,8 @@ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handle
|
||||
rotary.queue = rotary_encoder_create_queue();
|
||||
rotary_encoder_set_queue(&rotary.info, rotary.queue);
|
||||
|
||||
if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
|
||||
xQueueAddToSet( rotary.queue, button_queue_set );
|
||||
common_task_init();
|
||||
xQueueAddToSet( rotary.queue, common_queue_set );
|
||||
|
||||
// create companion button if rotary has a switch
|
||||
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
|
||||
@@ -360,3 +368,18 @@ bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handle
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Create Infrared
|
||||
*/
|
||||
bool create_infrared(int gpio, infrared_handler handler) {
|
||||
// initialize IR infrastructure
|
||||
infrared_init(&infrared.rb, gpio);
|
||||
infrared.handler = handler;
|
||||
|
||||
// join the queue set
|
||||
common_task_init();
|
||||
xRingbufferAddToQueueSetRead(infrared.rb, common_queue_set);
|
||||
|
||||
return (infrared.rb != NULL);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
/*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "infrared.h"
|
||||
|
||||
// button type (pressed = LOW or HIGH, matches GPIO level)
|
||||
#define BUTTON_LOW 0
|
||||
@@ -42,3 +34,5 @@ typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rota
|
||||
typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
|
||||
|
||||
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);
|
||||
|
||||
bool create_infrared(int gpio, infrared_handler handler);
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
177
components/services/infrared.c
Normal file
177
components/services/infrared.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* infrared receiver (using espressif's example)
|
||||
*
|
||||
* (c) Philippe G. 2020, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "infrared.h"
|
||||
|
||||
static const char* TAG = "IR";
|
||||
|
||||
#define RMT_RX_ACTIVE_LEVEL 0 /*!< If we connect with a IR receiver, the data is active low */
|
||||
|
||||
#define RMT_RX_CHANNEL 0 /*!< RMT channel for receiver */
|
||||
#define RMT_CLK_DIV 100 /*!< RMT counter clock divider */
|
||||
#define RMT_TICK_10_US (80000000/RMT_CLK_DIV/100000) /*!< RMT counter value for 10 us.(Source clock is APB clock) */
|
||||
|
||||
#define NEC_HEADER_HIGH_US 9000 /*!< NEC protocol header: positive 9ms */
|
||||
#define NEC_HEADER_LOW_US 4500 /*!< NEC protocol header: negative 4.5ms*/
|
||||
#define NEC_BIT_ONE_HIGH_US 560 /*!< NEC protocol data bit 1: positive 0.56ms */
|
||||
#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US) /*!< NEC protocol data bit 1: negative 1.69ms */
|
||||
#define NEC_BIT_ZERO_HIGH_US 560 /*!< NEC protocol data bit 0: positive 0.56ms */
|
||||
#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US) /*!< NEC protocol data bit 0: negative 0.56ms */
|
||||
#define NEC_BIT_MARGIN 150 /*!< NEC parse margin time */
|
||||
|
||||
#define NEC_ITEM_DURATION(d) ((d & 0x7fff)*10/RMT_TICK_10_US) /*!< Parse duration time from memory register value */
|
||||
#define NEC_DATA_ITEM_NUM 34 /*!< NEC code item number: header + 32bit data + end */
|
||||
#define rmt_item32_tIMEOUT_US 9500 /*!< RMT receiver timeout value(us) */
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
inline bool nec_check_in_range(int duration_ticks, int target_us, int margin_us) {
|
||||
if(( NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
|
||||
&& ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_header_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_bit_one_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static bool nec_bit_zero_if(rmt_item32_t* item) {
|
||||
if((item->level0 == RMT_RX_ACTIVE_LEVEL && item->level1 != RMT_RX_ACTIVE_LEVEL)
|
||||
&& nec_check_in_range(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
|
||||
&& nec_check_in_range(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static int nec_parse_items(rmt_item32_t* item, int item_num, uint16_t* addr, uint16_t* data) {
|
||||
int w_len = item_num;
|
||||
if(w_len < NEC_DATA_ITEM_NUM) {
|
||||
return -1;
|
||||
}
|
||||
int i = 0, j = 0;
|
||||
if(!nec_header_if(item++)) {
|
||||
return -1;
|
||||
}
|
||||
uint16_t addr_t = 0;
|
||||
for(j = 15; j >= 0; j--) {
|
||||
if(nec_bit_one_if(item)) {
|
||||
addr_t |= (1 << j);
|
||||
} else if(nec_bit_zero_if(item)) {
|
||||
addr_t |= (0 << j);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
item++;
|
||||
i++;
|
||||
}
|
||||
uint16_t data_t = 0;
|
||||
for(j = 15; j >= 0; j--) {
|
||||
if(nec_bit_one_if(item)) {
|
||||
data_t |= (1 << j);
|
||||
} else if(nec_bit_zero_if(item)) {
|
||||
data_t |= (0 << j);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
item++;
|
||||
i++;
|
||||
}
|
||||
*addr = addr_t;
|
||||
*data = data_t;
|
||||
return i;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void infrared_receive(RingbufHandle_t rb, infrared_handler handler) {
|
||||
size_t rx_size = 0;
|
||||
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 10 / portTICK_RATE_MS);
|
||||
|
||||
if (item) {
|
||||
uint16_t addr, cmd;
|
||||
int offset = 0;
|
||||
|
||||
while (1) {
|
||||
// parse data value from ringbuffer.
|
||||
int res = nec_parse_items(item + offset, rx_size / 4 - offset, &addr, &cmd);
|
||||
if (res > 0) {
|
||||
offset += res + 1;
|
||||
handler(addr, cmd);
|
||||
ESP_LOGD(TAG, "RMT RCV --- addr: 0x%04x cmd: 0x%04x", addr, cmd);
|
||||
} else break;
|
||||
}
|
||||
|
||||
// after parsing the data, return spaces to ringbuffer.
|
||||
vRingbufferReturnItem(rb, (void*) item);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio) {
|
||||
rmt_config_t rmt_rx;
|
||||
|
||||
ESP_LOGI(TAG, "Starting Infrared Receiver on gpio %d", gpio);
|
||||
|
||||
// initialize RMT driver
|
||||
rmt_rx.channel = RMT_RX_CHANNEL;
|
||||
rmt_rx.gpio_num = gpio;
|
||||
rmt_rx.clk_div = RMT_CLK_DIV;
|
||||
rmt_rx.mem_block_num = 1;
|
||||
rmt_rx.rmt_mode = RMT_MODE_RX;
|
||||
rmt_rx.rx_config.filter_en = true;
|
||||
rmt_rx.rx_config.filter_ticks_thresh = 100;
|
||||
rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
|
||||
rmt_config(&rmt_rx);
|
||||
rmt_driver_install(rmt_rx.channel, 1000, 0);
|
||||
|
||||
// get RMT RX ringbuffer
|
||||
rmt_get_ringbuf_handle(RMT_RX_CHANNEL, rb);
|
||||
rmt_rx_start(RMT_RX_CHANNEL, 1);
|
||||
}
|
||||
17
components/services/infrared.h
Normal file
17
components/services/infrared.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* (c) Philippe G. 2019, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
|
||||
typedef void (*infrared_handler)(uint16_t addr, uint16_t cmd);
|
||||
void infrared_receive(RingbufHandle_t rb, infrared_handler handler);
|
||||
void infrared_init(RingbufHandle_t *rb, int gpio);
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -175,24 +175,28 @@ bool spkfault_svc (void) {
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_jack_gpio(int gpio, char *value) {
|
||||
#ifndef CONFIG_JACK_LOCKED
|
||||
static void set_jack_gpio(int gpio, char *value) {
|
||||
if (strcasestr(value, "jack")) {
|
||||
char *p;
|
||||
jack.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) jack.active = atoi(p + 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
void set_spkfault_gpio(int gpio, char *value) {
|
||||
#ifndef CONFIG_SPKFAULT_LOCKED
|
||||
static void set_spkfault_gpio(int gpio, char *value) {
|
||||
if (strcasestr(value, "spkfault")) {
|
||||
char *p;
|
||||
spkfault.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) spkfault.active = atoi(p + 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ static decode_state alac_decode(void) {
|
||||
return DECODE_COMPLETE;
|
||||
}
|
||||
|
||||
// enough data for coding
|
||||
// is there enough data for decoding
|
||||
if (bytes < block_size) {
|
||||
UNLOCK_S;
|
||||
return DECODE_RUNNING;
|
||||
|
||||
@@ -1,100 +1,231 @@
|
||||
/*
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include "squeezelite.h"
|
||||
#include "config.h"
|
||||
#include "audio_controls.h"
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
|
||||
#define DOWN_OFS 0x10000
|
||||
#define UP_OFS 0x20000
|
||||
|
||||
// numbers are simply 0..9 but are not used
|
||||
|
||||
// arrow_right.down = 0001000e seems to be missing ...
|
||||
enum { BUTN_POWER_FRONT = 0X0A, BUTN_ARROW_UP, BUTN_ARROW_DOWN, BUTN_ARROW_LEFT, BUTN_KNOB_PUSH, BUTN_SEARCH,
|
||||
BUTN_REW, BUTN_FWD, BUTN_PLAY, BUTN_ADD, BUTN_BRIGHTNESS, BUTN_NOW_PLAYING,
|
||||
BUTN_PAUSE = 0X17, BUTN_BROWSE, BUTN_VOLUP_FRONT, BUTN_VOLDOWN_FRONT, BUTN_SIZE, BUTN_VISUAL, BUTN_VOLUMEMODE,
|
||||
BUTN_PRESET_1 = 0X23, BUTN_PRESET_2, BUTN_PRESET_3, BUTN_PRESET_4, BUTN_PRESET_5, BUTN_PRESET_6, BUTN_SNOOZE,
|
||||
BUTN_KNOB_LEFT = 0X5A, BUTN_KNOB_RIGHT };
|
||||
|
||||
#define BUTN_ARROW_RIGHT BUTN_KNOB_PUSH
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct BUTN_header {
|
||||
char opcode[4];
|
||||
u32_t length;
|
||||
u32_t jiffies;
|
||||
u32_t button;
|
||||
};
|
||||
|
||||
struct IR_header {
|
||||
char opcode[4];
|
||||
u32_t length;
|
||||
u32_t jiffies;
|
||||
u8_t format; // unused
|
||||
u8_t bits; // unused
|
||||
u32_t code;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
static in_addr_t server_ip;
|
||||
static u16_t server_hport;
|
||||
static u16_t server_cport;
|
||||
static u8_t mac[6];
|
||||
static void (*chained_notify)(in_addr_t, u16_t, u16_t);
|
||||
static bool raw_mode;
|
||||
|
||||
static void cli_send_cmd(char *cmd);
|
||||
|
||||
static void lms_volume_up(void) {
|
||||
cli_send_cmd("button volup");
|
||||
/****************************************************************************************
|
||||
* Send BUTN
|
||||
*/
|
||||
static void sendBUTN(int code, bool pressed) {
|
||||
struct BUTN_header pkt_header;
|
||||
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "BUTN", 4);
|
||||
|
||||
pkt_header.length = htonl(sizeof(pkt_header) - 8);
|
||||
pkt_header.jiffies = htonl(gettime_ms());
|
||||
pkt_header.button = htonl(code + (pressed ? DOWN_OFS : UP_OFS));
|
||||
|
||||
LOG_INFO("sending BUTN code %04x %s", code, pressed ? "down" : "up");
|
||||
|
||||
LOCK_P;
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void lms_volume_down(void) {
|
||||
cli_send_cmd("button voldown");
|
||||
/****************************************************************************************
|
||||
* Send IR
|
||||
*/
|
||||
static void sendIR(u16_t addr, u16_t cmd) {
|
||||
struct IR_header pkt_header;
|
||||
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "IR ", 4);
|
||||
|
||||
pkt_header.length = htonl(sizeof(pkt_header) - 8);
|
||||
pkt_header.jiffies = htonl(gettime_ms());
|
||||
pkt_header.format = pkt_header.bits = 0;
|
||||
pkt_header.code = htonl((addr << 16) | cmd);
|
||||
|
||||
LOG_INFO("sending IR code %04x", (addr << 16) | cmd);
|
||||
|
||||
LOCK_P;
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void lms_toggle(void) {
|
||||
cli_send_cmd("pause");
|
||||
static void lms_volume_up(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_VOLUP_FRONT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button volup");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_pause(void) {
|
||||
cli_send_cmd("pause 1");
|
||||
static void lms_volume_down(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_VOLDOWN_FRONT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button voldown");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_play(void) {
|
||||
cli_send_cmd("button play.single");
|
||||
static void lms_toggle(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_PAUSE, pressed);
|
||||
} else {
|
||||
cli_send_cmd("pause");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_stop(void) {
|
||||
static void lms_pause(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_PAUSE, pressed);
|
||||
} else {
|
||||
cli_send_cmd("pause 1");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_play(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_PLAY, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button play.single");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_stop(bool pressed) {
|
||||
cli_send_cmd("button stop");
|
||||
}
|
||||
|
||||
static void lms_rew(void) {
|
||||
cli_send_cmd("button rew.repeat");
|
||||
static void lms_rew(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_REW, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button rew.repeat");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_fwd(void) {
|
||||
cli_send_cmd("button fwd.repeat");
|
||||
static void lms_fwd(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_FWD, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button fwd.repeat");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_prev(void) {
|
||||
cli_send_cmd("button rew");
|
||||
static void lms_prev(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_REW, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button rew");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_next(void) {
|
||||
cli_send_cmd("button fwd");
|
||||
static void lms_next(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_FWD, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button fwd");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_up(void) {
|
||||
cli_send_cmd("button arrow_up");
|
||||
static void lms_up(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_ARROW_UP, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button arrow_up");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_down(void) {
|
||||
cli_send_cmd("button arrow_down");
|
||||
static void lms_down(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_ARROW_DOWN, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button arrow_down");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_left(void) {
|
||||
cli_send_cmd("button arrow_left");
|
||||
static void lms_left(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_ARROW_LEFT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button arrow_left");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_right(void) {
|
||||
cli_send_cmd("button arrow_right");
|
||||
static void lms_right(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_ARROW_RIGHT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button arrow_right");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_knob_left(void) {
|
||||
cli_send_cmd("button knob_left");
|
||||
static void lms_knob_left(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_KNOB_LEFT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button knob_left");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_knob_right(void) {
|
||||
cli_send_cmd("button knob_right");
|
||||
static void lms_knob_right(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_KNOB_RIGHT, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button knob_right");
|
||||
}
|
||||
}
|
||||
|
||||
static void lms_knob_push(void) {
|
||||
cli_send_cmd("button knob_push");
|
||||
static void lms_knob_push(bool pressed) {
|
||||
if (raw_mode) {
|
||||
sendBUTN(BUTN_KNOB_PUSH, pressed);
|
||||
} else {
|
||||
cli_send_cmd("button knob_push");
|
||||
}
|
||||
}
|
||||
|
||||
const actrls_t LMS_controls = {
|
||||
@@ -143,17 +274,33 @@ static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
server_ip = ip;
|
||||
server_hport = hport;
|
||||
server_cport = cport;
|
||||
|
||||
LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport);
|
||||
|
||||
if (chained_notify) (*chained_notify)(ip, hport, cport);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* IR handler
|
||||
*/
|
||||
static bool ir_handler(u16_t addr, u16_t cmd) {
|
||||
sendIR(addr, cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Initialize controls - shall be called once from output_init_embedded
|
||||
*/
|
||||
void sb_controls_init(void) {
|
||||
LOG_INFO("initializing CLI controls");
|
||||
char *p = config_alloc_get_default(NVS_TYPE_STR, "lms_ctrls_raw", "n", 0);
|
||||
raw_mode = p && (*p == '1' || *p == 'Y' || *p == 'y');
|
||||
free(p);
|
||||
|
||||
LOG_INFO("initializing audio (buttons/rotary/ir) controls (raw:%u)", raw_mode);
|
||||
|
||||
get_mac(mac);
|
||||
actrls_set_default(LMS_controls, NULL);
|
||||
actrls_set_default(LMS_controls, raw_mode, NULL, ir_handler);
|
||||
|
||||
chained_notify = server_notify;
|
||||
server_notify = notify;
|
||||
}
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -44,15 +34,15 @@ static bool enable_bt_sink;
|
||||
static bool enable_airplay;
|
||||
|
||||
#define RAOP_OUTPUT_SIZE (RAOP_SAMPLE_RATE * 2 * 2 * 2 * 1.2)
|
||||
#define SYNC_WIN_RUN 32
|
||||
#define SYNC_WIN_SLOW 32
|
||||
#define SYNC_WIN_CHECK 8
|
||||
#define SYNC_WIN_START 2
|
||||
#define SYNC_WIN_FAST 2
|
||||
|
||||
static raop_event_t raop_state;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
bool enabled;
|
||||
int sum, count, win, errors[SYNC_WIN_RUN];
|
||||
int sum, count, win, errors[SYNC_WIN_SLOW];
|
||||
s32_t len;
|
||||
u32_t start_time, playtime;
|
||||
} raop_sync;
|
||||
@@ -200,48 +190,31 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
// this is async, so player might have been deleted
|
||||
switch (event) {
|
||||
case RAOP_TIMING: {
|
||||
u32_t ms, now = gettime_ms();
|
||||
int error;
|
||||
|
||||
if (!raop_sync.enabled || output.state < OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
|
||||
|
||||
// first must make sure we started on time
|
||||
if (raop_sync.win == SYNC_WIN_START) {
|
||||
// how many ms have we really played
|
||||
ms = now - output.updated + ((output.frames_played_dmp - output.device_frames) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
error = ms - (now - raop_sync.start_time);
|
||||
if (!raop_sync.enabled || output.state != OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break;
|
||||
|
||||
u32_t ms, now = gettime_ms();
|
||||
u32_t level = _buf_used(outputbuf);
|
||||
int error;
|
||||
|
||||
LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, error);
|
||||
} else {
|
||||
u32_t level = _buf_used(outputbuf);
|
||||
// in how many ms will the most recent block play
|
||||
ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
|
||||
|
||||
// in how many ms will the most recent block play
|
||||
ms = (((s32_t)(level - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 10) / (RAOP_SAMPLE_RATE / 100) - (s32_t) (now - output.updated);
|
||||
// when outputbuf is empty, it means we have a network black-out or something
|
||||
error = level ? (raop_sync.playtime - now) - ms : 0;
|
||||
|
||||
// when outputbuf is empty, it means we have a network black-out or something
|
||||
error = level ? (raop_sync.playtime - now) - ms : 0;
|
||||
|
||||
if (loglevel == lDEBUG || !level) {
|
||||
LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
|
||||
}
|
||||
if (loglevel == lDEBUG || !level) {
|
||||
LOG_INFO("head local:%d, remote:%d (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
LOG_INFO("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process);
|
||||
}
|
||||
|
||||
// calculate sum, error and update sliding window
|
||||
raop_sync.errors[raop_sync.count++ % raop_sync.win] = error;
|
||||
raop_sync.sum += error;
|
||||
error = raop_sync.sum / min(raop_sync.count, raop_sync.win);
|
||||
|
||||
// move to normal mode if possible
|
||||
if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) {
|
||||
raop_sync.win = SYNC_WIN_RUN;
|
||||
LOG_INFO("switching to slow sync mode %u", raop_sync.win);
|
||||
}
|
||||
|
||||
// wait till e have enough data or there is a strong deviation
|
||||
if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
|
||||
|
||||
// correct if needed
|
||||
// wait till we have enough data or there is a strong deviation
|
||||
if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {
|
||||
if (error < 0) {
|
||||
output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000;
|
||||
output.state = OUTPUT_SKIP_FRAMES;
|
||||
@@ -251,11 +224,18 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
output.state = OUTPUT_PAUSE_FRAMES;
|
||||
LOG_INFO("pausing for %u frames (count: %d)", output.pause_frames, raop_sync.count);
|
||||
}
|
||||
|
||||
// reset sliding window
|
||||
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
|
||||
}
|
||||
|
||||
// move to normal mode if possible
|
||||
if (raop_sync.win == 1) {
|
||||
raop_sync.win = SYNC_WIN_FAST;
|
||||
LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, raop_sync.playtime - now, error);
|
||||
} else if (raop_sync.win == SYNC_WIN_FAST && raop_sync.count >= SYNC_WIN_FAST && abs(error) < 10) {
|
||||
raop_sync.win = SYNC_WIN_SLOW;
|
||||
LOG_INFO("switching to slow sync mode %u", raop_sync.win);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -272,8 +252,8 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
case RAOP_STREAM:
|
||||
LOG_INFO("Stream", NULL);
|
||||
raop_state = event;
|
||||
raop_sync.win = SYNC_WIN_START;
|
||||
raop_sync.sum = raop_sync.count = 0 ;
|
||||
raop_sync.win = 1;
|
||||
raop_sync.sum = raop_sync.count = 0;
|
||||
memset(raop_sync.errors, 0, sizeof(raop_sync.errors));
|
||||
raop_sync.enabled = !strcasestr(output.device, "BT");
|
||||
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
/*
|
||||
* (c) 2004,2006 Richard Titmuss for SlimProtoLib
|
||||
* (c) 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -208,23 +197,20 @@ extern const uint8_t vu_bitmap[] asm("_binary_vu_data_start");
|
||||
#define ANIM_SCREEN_1 0x04
|
||||
#define ANIM_SCREEN_2 0x08
|
||||
|
||||
static u8_t ANIC_resp = ANIM_NONE;
|
||||
static uint16_t SETD_width;
|
||||
|
||||
#define SCROLL_STACK_SIZE (3*1024)
|
||||
#define LINELEN 40
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
|
||||
static bool (*slimp_handler_chain)(u8_t *data, int len);
|
||||
static void (*slimp_loop_chain)(void);
|
||||
static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
|
||||
static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
|
||||
|
||||
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
static void server(in_addr_t ip, u16_t hport, u16_t cport);
|
||||
static void send_server(void);
|
||||
static void sendSETD(u16_t width, u16_t height);
|
||||
static void sendANIC(u8_t code);
|
||||
static bool handler(u8_t *data, int len);
|
||||
static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
|
||||
static void vfdc_handler( u8_t *_data, int bytes_read);
|
||||
@@ -234,7 +220,6 @@ static void grfs_handler(u8_t *data, int len);
|
||||
static void grfg_handler(u8_t *data, int len);
|
||||
static void grfa_handler(u8_t *data, int len);
|
||||
static void visu_handler(u8_t *data, int len);
|
||||
|
||||
static void displayer_task(void* arg);
|
||||
|
||||
/* scrolling undocumented information
|
||||
@@ -304,11 +289,13 @@ bool sb_display_init(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// inform LMS of our screen dimensions
|
||||
sendSETD(GDS_GetWidth(display), GDS_GetHeight(display));
|
||||
|
||||
// need to force height to 32 maximum
|
||||
displayer.width = GDS_GetWidth(display);
|
||||
displayer.height = min(GDS_GetHeight(display), SB_HEIGHT);
|
||||
SETD_width = displayer.width;
|
||||
|
||||
|
||||
// create visu configuration
|
||||
visu.bar_gap = 1;
|
||||
visu.speed = 100;
|
||||
@@ -330,9 +317,6 @@ bool sb_display_init(void) {
|
||||
slimp_handler_chain = slimp_handler;
|
||||
slimp_handler = handler;
|
||||
|
||||
slimp_loop_chain = slimp_loop;
|
||||
slimp_loop = send_server;
|
||||
|
||||
notify_chain = server_notify;
|
||||
server_notify = server;
|
||||
|
||||
@@ -369,52 +353,44 @@ static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) {
|
||||
else return true;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* Send message to server (ANIC at that time)
|
||||
* Send ANImation Complete
|
||||
*/
|
||||
static void send_server(void) {
|
||||
/*
|
||||
This complication is needed as we cannot send direclty to LMS, because
|
||||
send_packet is not thread safe. So must subscribe to slimproto busy loop
|
||||
end send from there
|
||||
*/
|
||||
if (ANIC_resp != ANIM_NONE) {
|
||||
struct ANIC_header pkt_header;
|
||||
static void sendANIC(u8_t code) {
|
||||
struct ANIC_header pkt_header;
|
||||
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "ANIC", 4);
|
||||
pkt_header.length = htonl(sizeof(pkt_header) - 8);
|
||||
pkt_header.mode = ANIC_resp;
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "ANIC", 4);
|
||||
pkt_header.length = htonl(sizeof(pkt_header) - 8);
|
||||
pkt_header.mode = code;
|
||||
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
LOCK_P;
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
ANIC_resp = ANIM_NONE;
|
||||
}
|
||||
|
||||
if (SETD_width) {
|
||||
struct SETD_header pkt_header;
|
||||
/****************************************************************************************
|
||||
* Send SETD for width
|
||||
*/
|
||||
static void sendSETD(u16_t width, u16_t height) {
|
||||
struct SETD_header pkt_header;
|
||||
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "SETD", 4);
|
||||
memset(&pkt_header, 0, sizeof(pkt_header));
|
||||
memcpy(&pkt_header.opcode, "SETD", 4);
|
||||
|
||||
pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
|
||||
pkt_header.length = htonl(sizeof(pkt_header) + 4 - 8);
|
||||
pkt_header.id = 0xfe; // id 0xfe is width S:P:Squeezebox2
|
||||
pkt_header.length = htonl(sizeof(pkt_header) + 4 - 8);
|
||||
|
||||
u16_t height = GDS_GetHeight(display);
|
||||
LOG_INFO("sending dimension %ux%u", SETD_width, height);
|
||||
LOG_INFO("sending dimension %ux%u", width, height);
|
||||
|
||||
SETD_width = htons(SETD_width);
|
||||
height = htons(height);
|
||||
width = htons(width);
|
||||
height = htons(height);
|
||||
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
send_packet((uint8_t *) &SETD_width, 2);
|
||||
send_packet((uint8_t *) &height, 2);
|
||||
|
||||
SETD_width = 0;
|
||||
}
|
||||
|
||||
if (slimp_loop_chain) (*slimp_loop_chain)();
|
||||
LOCK_P;
|
||||
send_packet((uint8_t *) &pkt_header, sizeof(pkt_header));
|
||||
send_packet((uint8_t *) &width, 2);
|
||||
send_packet((uint8_t *) &height, 2);
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -427,11 +403,13 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
|
||||
sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
|
||||
if (displayer.owned) GDS_TextPos(display, GDS_FONT_DEFAULT, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg);
|
||||
SETD_width = displayer.width;
|
||||
displayer.dirty = true;
|
||||
|
||||
xSemaphoreGive(displayer.mutex);
|
||||
|
||||
// inform new LMS server of our capabilities
|
||||
sendSETD(displayer.width, GDS_GetHeight(display));
|
||||
|
||||
if (notify_chain) (*notify_chain)(ip, hport, cport);
|
||||
}
|
||||
|
||||
@@ -1152,8 +1130,7 @@ static void displayer_task(void *args) {
|
||||
|
||||
// see if we need to pause or if we are done
|
||||
if (scroller.mode) {
|
||||
// can't call directly send_packet from slimproto as it's not re-entrant
|
||||
ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1;
|
||||
sendANIC(ANIM_SCROLL_ONCE | ANIM_SCREEN_1);
|
||||
LOG_INFO("scroll-once terminated");
|
||||
} else {
|
||||
scroller.wake = scroller.pause;
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
#include "squeezelite.h"
|
||||
@@ -25,6 +15,8 @@
|
||||
#include "esp_timer.h"
|
||||
#include "esp_wifi.h"
|
||||
|
||||
mutex_type slimp_mutex;
|
||||
|
||||
void get_mac(u8_t mac[]) {
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||
}
|
||||
@@ -56,6 +48,7 @@ extern bool sb_display_init(void);
|
||||
u8_t custom_player_id = 12;
|
||||
|
||||
void embedded_init(void) {
|
||||
mutex_create(slimp_mutex);
|
||||
sb_controls_init();
|
||||
if (sb_display_init()) custom_player_id = 100;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ void embedded_init(void);
|
||||
void register_external(void);
|
||||
void deregister_external(void);
|
||||
void decode_restore(int external);
|
||||
// used when other client wants to use slimproto socket to send messages
|
||||
extern mutex_type slimp_mutex;
|
||||
#define LOCK_P mutex_lock(slimp_mutex)
|
||||
#define UNLOCK_P mutex_unlock(slimp_mutex)
|
||||
|
||||
// must provide or define as 0xffff
|
||||
u16_t get_RSSI(void);
|
||||
@@ -80,5 +84,5 @@ void output_visu_close(void);
|
||||
bool (*slimp_handler)(u8_t *data, int len);
|
||||
void (*slimp_loop)(void);
|
||||
void (*server_notify)(in_addr_t ip, u16_t hport, u16_t cport);
|
||||
|
||||
|
||||
#endif // EMBEDDED_H
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) Philippe G. 2020, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* (c) Philippe G. 2020, 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
14
components/squeezelite/external/dac_external.c
vendored
14
components/squeezelite/external/dac_external.c
vendored
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ static decode_state helixaac_decode(void) {
|
||||
u8_t *sptr;
|
||||
bool endstream;
|
||||
frames_t frames;
|
||||
|
||||
|
||||
LOCK_S;
|
||||
bytes_total = _buf_used(streambuf);
|
||||
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
|
||||
@@ -389,19 +389,25 @@ static decode_state helixaac_decode(void) {
|
||||
}
|
||||
|
||||
if (found == 1) {
|
||||
|
||||
LOG_INFO("samplerate: %u channels: %u", samplerate, channels);
|
||||
bytes_total = _buf_used(streambuf);
|
||||
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
|
||||
|
||||
LOCK_O;
|
||||
LOG_INFO("setting track_start");
|
||||
output.next_sample_rate = decode_newstream(samplerate, output.supported_rates);
|
||||
IF_DSD( output.next_fmt = PCM; )
|
||||
output.track_start = outputbuf->writep;
|
||||
if (output.fade_mode) _checkfade(true);
|
||||
decode.new_stream = false;
|
||||
UNLOCK_O;
|
||||
|
||||
LOG_INFO("setting track start, samplerate: %u channels: %u", samplerate, channels);
|
||||
|
||||
bytes_total = _buf_used(streambuf);
|
||||
bytes_wrap = min(bytes_total, _buf_cont_read(streambuf));
|
||||
|
||||
// come back later if we don' thave enough data
|
||||
if (bytes_total < WRAPBUF_LEN) {
|
||||
UNLOCK_S;
|
||||
LOG_INFO("need more audio data");
|
||||
return DECODE_RUNNING;
|
||||
}
|
||||
|
||||
} else if (found == -1) {
|
||||
|
||||
@@ -418,15 +424,15 @@ static decode_state helixaac_decode(void) {
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_wrap < WRAPBUF_LEN && bytes_total > WRAPBUF_LEN) {
|
||||
// make a local copy of frames which may have wrapped round the end of streambuf
|
||||
// we always have at least WRAPBUF_LEN unless it's the end of a stream
|
||||
if (bytes_wrap < WRAPBUF_LEN) {
|
||||
// build a linear buffer if we are crossing the end of streambuf
|
||||
memcpy(a->wrap_buf, streambuf->readp, bytes_wrap);
|
||||
memcpy(a->wrap_buf + bytes_wrap, streambuf->buf, WRAPBUF_LEN - bytes_wrap);
|
||||
memcpy(a->wrap_buf + bytes_wrap, streambuf->buf, min(WRAPBUF_LEN, bytes_total) - bytes_wrap);
|
||||
|
||||
sptr = a->wrap_buf;
|
||||
bytes = bytes_wrap = WRAPBUF_LEN;
|
||||
bytes = bytes_wrap = min(WRAPBUF_LEN, bytes_total);
|
||||
} else {
|
||||
|
||||
sptr = streambuf->readp;
|
||||
bytes = bytes_wrap;
|
||||
}
|
||||
@@ -442,9 +448,8 @@ static decode_state helixaac_decode(void) {
|
||||
bytes = bytes_wrap - bytes;
|
||||
endstream = false;
|
||||
|
||||
// mp4 end of chunk - skip to next offset
|
||||
if (a->chunkinfo && a->chunkinfo[a->nextchunk].offset && a->sample++ == a->chunkinfo[a->nextchunk].sample) {
|
||||
|
||||
// mp4 end of chunk - skip to next offset
|
||||
if (a->chunkinfo[a->nextchunk].offset > a->pos) {
|
||||
u32_t skip = a->chunkinfo[a->nextchunk].offset - a->pos;
|
||||
if (skip != bytes) {
|
||||
@@ -461,15 +466,12 @@ static decode_state helixaac_decode(void) {
|
||||
LOG_ERROR("error: need to skip backwards!");
|
||||
endstream = true;
|
||||
}
|
||||
|
||||
// adts and mp4 when not at end of chunk
|
||||
} else if (bytes > 0) {
|
||||
|
||||
// adts and mp4 when not at end of chunk
|
||||
_buf_inc_readp(streambuf, bytes);
|
||||
a->pos += bytes;
|
||||
|
||||
// error which doesn't advance streambuf - end
|
||||
} else {
|
||||
// error which doesn't advance streambuf - end
|
||||
endstream = true;
|
||||
}
|
||||
|
||||
@@ -517,7 +519,7 @@ static decode_state helixaac_decode(void) {
|
||||
frames_t f;
|
||||
frames_t count;
|
||||
ISAMPLE_T *optr;
|
||||
|
||||
|
||||
IF_DIRECT(
|
||||
f = _buf_cont_write(outputbuf) / BYTES_PER_FRAME;
|
||||
optr = (ISAMPLE_T *)outputbuf->writep;
|
||||
@@ -559,7 +561,7 @@ static decode_state helixaac_decode(void) {
|
||||
if (frames) LOG_ERROR("unhandled case");
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
UNLOCK_O_direct;
|
||||
|
||||
return DECODE_RUNNING;
|
||||
@@ -570,13 +572,15 @@ static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
||||
|
||||
a->type = size;
|
||||
a->pos = a->consume = a->sample = a->nextchunk = 0;
|
||||
|
||||
|
||||
if (a->chunkinfo) {
|
||||
free(a->chunkinfo);
|
||||
}
|
||||
|
||||
if (a->stsc) {
|
||||
free(a->stsc);
|
||||
}
|
||||
|
||||
a->chunkinfo = NULL;
|
||||
a->stsc = NULL;
|
||||
a->skip = 0;
|
||||
@@ -585,12 +589,14 @@ static void helixaac_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
||||
a->empty = false;
|
||||
|
||||
if (a->hAac) {
|
||||
HAAC(a, FlushCodec, a->hAac);
|
||||
// always free decoder as flush only works when no parameter has changed
|
||||
HAAC(a, FreeDecoder, a->hAac);
|
||||
} else {
|
||||
a->hAac = HAAC(a, InitDecoder);
|
||||
a->write_buf = malloc(FRAME_BUF * BYTES_PER_FRAME);
|
||||
a->wrap_buf = malloc(WRAPBUF_LEN);
|
||||
}
|
||||
|
||||
a->hAac = HAAC(a, InitDecoder);
|
||||
}
|
||||
|
||||
static void helixaac_close(void) {
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
#include "squeezelite.h"
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ event_event wake_e;
|
||||
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||
#define LOCK_D mutex_lock(decode.mutex)
|
||||
#define UNLOCK_D mutex_unlock(decode.mutex)
|
||||
#if !EMBEDDED
|
||||
#define LOCK_P
|
||||
#define UNLOCK_P
|
||||
#endif
|
||||
#if IR
|
||||
#define LOCK_I mutex_lock(ir.mutex)
|
||||
#define UNLOCK_I mutex_unlock(ir.mutex)
|
||||
@@ -149,11 +153,12 @@ static void sendHELO(bool reconnect, const char *fixed_cap, const char *var_cap,
|
||||
LOG_INFO("mac: %02x:%02x:%02x:%02x:%02x:%02x", pkt.mac[0], pkt.mac[1], pkt.mac[2], pkt.mac[3], pkt.mac[4], pkt.mac[5]);
|
||||
|
||||
LOG_INFO("cap: %s%s%s", base_cap, fixed_cap, var_cap);
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt, sizeof(pkt));
|
||||
send_packet((u8_t *)base_cap, strlen(base_cap));
|
||||
send_packet((u8_t *)fixed_cap, strlen(fixed_cap));
|
||||
send_packet((u8_t *)var_cap, strlen(var_cap));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void sendSTAT(const char *event, u32_t server_timestamp) {
|
||||
@@ -205,7 +210,9 @@ static void sendSTAT(const char *event, u32_t server_timestamp) {
|
||||
ms_played - now + status.stream_start, status.device_frames * 1000 / status.current_sample_rate, now - status.updated);
|
||||
}
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt, sizeof(pkt));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void sendDSCO(disconnect_code disconnect) {
|
||||
@@ -218,7 +225,9 @@ static void sendDSCO(disconnect_code disconnect) {
|
||||
|
||||
LOG_DEBUG("DSCO: %d", disconnect);
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt, sizeof(pkt));
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void sendRESP(const char *header, size_t len) {
|
||||
@@ -229,9 +238,11 @@ static void sendRESP(const char *header, size_t len) {
|
||||
pkt_header.length = htonl(sizeof(pkt_header) + len - 8);
|
||||
|
||||
LOG_DEBUG("RESP");
|
||||
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
|
||||
send_packet((u8_t *)header, len);
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void sendMETA(const char *meta, size_t len) {
|
||||
@@ -243,8 +254,10 @@ static void sendMETA(const char *meta, size_t len) {
|
||||
|
||||
LOG_DEBUG("META");
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
|
||||
send_packet((u8_t *)meta, len);
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
static void sendSETDName(const char *name) {
|
||||
@@ -258,8 +271,10 @@ static void sendSETDName(const char *name) {
|
||||
|
||||
LOG_DEBUG("set playername: %s", name);
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt_header, sizeof(pkt_header));
|
||||
send_packet((u8_t *)name, strlen(name) + 1);
|
||||
UNLOCK_P;
|
||||
}
|
||||
|
||||
#if IR
|
||||
@@ -274,8 +289,9 @@ void sendIR(u32_t code, u32_t ts) {
|
||||
pkt.ir_code = htonl(code);
|
||||
|
||||
LOG_DEBUG("IR: ir code: 0x%x ts: %u", code, ts);
|
||||
|
||||
LOCK_P;
|
||||
send_packet((u8_t *)&pkt, sizeof(pkt));
|
||||
UNLOCK_P;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -3,18 +3,8 @@
|
||||
*
|
||||
* 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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
@@ -4,18 +4,8 @@
|
||||
* (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/>.
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user