Initial public release of the 2024A software.

This commit is contained in:
Joe Kearney 2025-01-25 14:04:42 -06:00
parent 7b9ad3edfd
commit 303e9e1dad
361 changed files with 60083 additions and 2 deletions

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# ---> esp-idf
# gitignore template for esp-idf, the official development framework for ESP32
# https://github.com/espressif/esp-idf
build/
sdkconfig.old
.vscode/*

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "components/SystemK"]
path = components/SystemK
url = https://git.ktag.clubk.club/Software/SystemK

9
CMakeLists.txt Normal file
View file

@ -0,0 +1,9 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(2024A-SW)

View file

@ -1,3 +1,43 @@
# 2024A-SW
# 2024A Software
Software for the 2024A using the ESP-IDF.
## Overview
This is software for the [2024A](https://git.clubk.club/KTag/2024A-HW) based on the
[ESP-IDF](https://github.com/espressif/esp-idf/).
## License: [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html)
This software is part of the KTag project.
🛡️ <https://ktag.clubk.club> 🃞
Copyright © 2023-2025 Joseph P. Kearney and the KTag developers.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero 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 Affero General Public License for more
details.
There should be a copy of the GNU Affero General Public License in the [LICENSE](LICENSE)
file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
## Open-Source Software
This software in turn makes use of the following open-source software libraries and components:
| Name | Version | License ([SPDX](https://spdx.org/licenses/)) | URL
|----------------------------|--------:|--------------------------------------------------------------------------|---------------------------------------------
| SystemK | 1.0 | [AGPL-3.0-or-later](https://spdx.org/licenses/AGPL-3.0-or-later.html) | https://git.clubk.club/KTag/SystemK
| ESP-IDF | 5.4.0 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://github.com/espressif/esp-idf/
| espressif/button | 3.5.0 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://components.espressif.com/components/espressif/button
| espressif/led_strip | 2.5.3 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://components.espressif.com/components/espressif/led_strip
| espressif/usb_host_msc | 1.1.3 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://components.espressif.com/components/espressif/usb_host_msc
| espressif/mdns | 1.4.3 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://components.espressif.com/components/espressif/mdns
| chmorgan/esp-audio-player | 1.0.7 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://components.espressif.com/components/chmorgan/esp-audio-player
| esp-libhelix-mp3 | 1.0.3 | [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html) | https://github.com/chmorgan/esp-libhelix-mp3
| libhelix-mp3 | f443079 | [RPSL](https://github.com/chmorgan/libhelix-mp3/blob/master/LICENSE.txt) | https://github.com/chmorgan/libhelix-mp3

View file

@ -0,0 +1,11 @@
idf_component_register(
SRCS
"I2S_Audio.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"driver"
"spiffs"
"esp-audio-player"
)

View file

@ -0,0 +1,462 @@
#include <SystemK.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <driver/i2s_std.h>
#include <esp_spiffs.h>
#include <audio_player.h>
// I2S Configuration
#define I2S_DOUT GPIO_NUM_11
#define I2S_BCLK GPIO_NUM_12
#define I2S_LRC GPIO_NUM_13
#define I2S_DIN I2S_GPIO_UNUSED
#define I2S_SCLK I2S_GPIO_UNUSED
#define AUDIO_TASK_STACK_SIZE 4096
static StackType_t xStack[AUDIO_TASK_STACK_SIZE];
static StaticTask_t xTaskBuffer;
static QueueHandle_t xQueueAudio;
static TaskHandle_t xAudioTaskHandle;
void I2SAudioTask(void *pvParameters);
static const char *TAG = "I2S Audio";
static i2s_chan_handle_t tx_handle;
#define I2S_GPIO_CFG \
{ \
.mclk = I2S_SCLK, \
.bclk = I2S_BCLK, \
.ws = I2S_LRC, \
.dout = I2S_DOUT, \
.din = I2S_DIN, \
.invert_flags = { \
.mclk_inv = false, \
.bclk_inv = false, \
.ws_inv = false, \
}, \
}
#define I2S_DUPLEX_STEREO_CFG(_sample_rate) \
{ \
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), \
.gpio_cfg = I2S_GPIO_CFG, \
}
static esp_err_t I2S_Audio_Set_Mute(AUDIO_PLAYER_MUTE_SETTING setting)
{
if (setting == AUDIO_PLAYER_MUTE)
{
KLOG_DEBUG(TAG, "Muted.");
}
else
{
KLOG_DEBUG(TAG, "Muted.");
}
return ESP_OK;
}
static esp_err_t I2S_Audio_Write(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms)
{
esp_err_t ret = ESP_OK;
ret = i2s_channel_write(tx_handle, (char *)audio_buffer, len, bytes_written, timeout_ms);
return ret;
}
static esp_err_t I2S_Audio_Reconfigure_Clock(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch)
{
KLOG_INFO(TAG, "Reconfiguring clock for %lu sps, %lu bps, %d channels.", rate, bits_cfg, ch);
esp_err_t ret = ESP_OK;
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate),
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bits_cfg, (i2s_slot_mode_t)ch),
.gpio_cfg = I2S_GPIO_CFG};
ret |= i2s_channel_disable(tx_handle);
ret |= i2s_channel_reconfig_std_clock(tx_handle, &std_cfg.clk_cfg);
ret |= i2s_channel_reconfig_std_slot(tx_handle, &std_cfg.slot_cfg);
ret |= i2s_channel_enable(tx_handle);
return ret;
}
void Initialize_Audio(void)
{
KLOG_INFO(TAG, "Initializing I2S Audio...");
// Setup a standard configuration and the channel.
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer.
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
// Setup the I2S configuration.
i2s_std_config_t std_cfg = I2S_DUPLEX_STEREO_CFG(44100);
esp_err_t ret = i2s_channel_init_std_mode(tx_handle, &std_cfg);
if (ret != ESP_OK)
{
if (ret == ESP_ERR_NO_MEM)
{
KLOG_ERROR(TAG, "No memory for storing the channel information");
}
else if (ret == ESP_ERR_INVALID_ARG)
{
KLOG_ERROR(TAG, "NULL pointer or invalid configuration");
}
else if (ret == ESP_ERR_INVALID_STATE)
{
KLOG_ERROR(TAG, "This channel is not registered");
}
else
{
KLOG_ERROR(TAG, "Failed to initialize I2S Audio (%s)", esp_err_to_name(ret));
}
}
i2s_channel_enable(tx_handle);
xQueueAudio = xQueueCreate(5, sizeof(AudioAction_T));
if (xQueueAudio == NULL)
{
KLOG_ERROR(TAG, "Couldn't create the I2S Audio queue.");
}
audio_player_config_t config = {.mute_fn = I2S_Audio_Set_Mute,
.write_fn = I2S_Audio_Write,
.clk_set_fn = I2S_Audio_Reconfigure_Clock,
.priority = 10,
.coreID = APP_CPU_NUM};
ret = audio_player_new(config);
if (ret != ESP_OK)
{
KLOG_ERROR(TAG, "Couldn't create the audio player.");
}
xAudioTaskHandle = xTaskCreateStaticPinnedToCore(
I2SAudioTask, // Function that implements the task.
"I2S Audio", // Text name for the task.
AUDIO_TASK_STACK_SIZE, // Stack size in words, not bytes.
NULL, // Parameter passed into the task.
1, // Priority at which the task is created.
xStack, // Array to use as the task's stack.
&xTaskBuffer, // Variable to hold the task's data structure.
APP_CPU_NUM); // Core where the task should run.
if (xAudioTaskHandle == NULL)
{
KLOG_ERROR(TAG, "Couldn't create the I2S Audio task.");
}
}
esp_err_t Play_Audio_File(const char *filename)
{
KLOG_INFO(TAG, "Attempting to play file: %s", filename);
KLOG_DEBUG(TAG, "Free heap: %ld bytes", esp_get_free_heap_size());
FILE *fh = fopen(filename, "rb");
if (fh == NULL)
{
KLOG_ERROR(TAG, "Failed to open audio file %s! Error: %s", filename, strerror(errno));
return ESP_ERR_NOT_FOUND;
}
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
KLOG_DEBUG(TAG, "File size: %ld bytes", file_size);
esp_err_t ret = audio_player_play(fh);
if (ret != ESP_OK)
{
KLOG_ERROR(TAG, "Couldn't play audio file %s! Error: %d", filename, ret);
fclose(fh);
return ret;
}
return ESP_OK;
}
SystemKResult_T Perform_Audio_Action(AudioAction_T *action)
{
if (xQueueSend(xQueueAudio, action, 0) == pdTRUE)
{
return SYSTEMK_RESULT_SUCCESS;
}
else
{
return SYSTEMK_RESULT_QUEUE_IS_FULL;
}
}
char *concat_path(const char *directory_path, const char *filename)
{
size_t needed = snprintf(NULL, 0, "%s/%s", directory_path, filename) + 1;
char *full_path = malloc(needed);
if (full_path != NULL)
{
snprintf(full_path, needed, "%s/%s", directory_path, filename);
}
return full_path;
}
char *find_filename(const char *directory_path, const char *prefix)
{
DIR *dir = opendir(directory_path);
if (dir == NULL)
{
KLOG_ERROR(TAG, "Failed to open directory '%s': %s!", directory_path, strerror(errno));
return NULL;
}
struct dirent *entry;
char *filename = NULL;
while ((entry = readdir(dir)) != NULL)
{
if (entry->d_type == DT_REG && strncmp(entry->d_name, prefix, strlen(prefix)) == 0)
{
filename = strdup(entry->d_name);
if (filename == NULL)
{
KLOG_ERROR(TAG, "Memory allocation failed!");
}
else
{
KLOG_DEBUG(TAG, "Found matching file: %s", filename);
}
break;
}
}
closedir(dir);
if (filename == NULL)
{
KLOG_WARN(TAG, "No file beginning with '%s' found in directory '%s'!", prefix, directory_path);
}
return filename;
}
SystemKResult_T Play_Sound_By_Prefix(const char *prefix)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
const char *sounds_dir = "/usb/01";
char *filename = find_filename(sounds_dir, prefix);
if (filename != NULL)
{
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
{
audio_player_stop();
}
Play_Audio_File(concat_path(sounds_dir, filename));
}
else
{
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
}
return result;
}
SystemKResult_T Play_Number_Sound(uint8_t number)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
char number_str[4];
snprintf(number_str, 4, "%03u", number);
const char *sounds_dir = "/usb/10";
char *filename = find_filename(sounds_dir, (const char *)number_str);
if (filename != NULL)
{
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
{
audio_player_stop();
}
Play_Audio_File(concat_path(sounds_dir, filename));
}
else
{
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
}
return result;
}
void I2SAudioTask(void *pvParameters)
{
// Wait for system initialization.
// TODO: Make this closed-loop!
vTaskDelay(1000 / portTICK_PERIOD_MS);
while (true)
{
AudioAction_T action;
if (xQueueReceive(xQueueAudio, &action, portMAX_DELAY) == pdPASS)
{
switch (action.ID)
{
case AUDIO_SET_VOLUME:
break;
case AUDIO_SILENCE:
if (audio_player_get_state() == AUDIO_PLAYER_STATE_PLAYING)
{
audio_player_stop();
}
break;
case AUDIO_PLAY_STARTUP_SOUND:
Play_Sound_By_Prefix("001");
break;
case AUDIO_PLAY_SHOT_FIRED:
Play_Sound_By_Prefix("002");
break;
case AUDIO_PLAY_TAG_RECEIVED:
Play_Sound_By_Prefix("003");
break;
case AUDIO_PLAY_TAGGED_OUT:
Play_Sound_By_Prefix("004");
break;
case AUDIO_PLAY_MISFIRE:
Play_Sound_By_Prefix("005");
break;
case AUDIO_PRONOUNCE_NUMBER_0_TO_100:
uint8_t number = *((uint8_t *)action.Data);
if (number > 100)
{
number = 100;
}
else if (number == 0)
{
number = 101;
}
Play_Number_Sound(number);
break;
case AUDIO_PLAY_MENU_PROMPT:
Play_Sound_By_Prefix("006");
break;
case AUDIO_PLAY_SELECTION_INDICATOR:
Play_Sound_By_Prefix("007");
break;
case AUDIO_PLAY_HEALTH_REMAINING:
Play_Sound_By_Prefix("008");
break;
case AUDIO_PLAY_ELECTRONIC_DANCE_MUSIC:
Play_Sound_By_Prefix("009");
break;
case AUDIO_PLAY_GENERIC_ERROR:
Play_Sound_By_Prefix("010");
break;
case AUDIO_PLAY_VOLUME_PROMPT:
Play_Sound_By_Prefix("011");
break;
case AUDIO_PLAY_RIGHT_HANDED:
Play_Sound_By_Prefix("012");
break;
case AUDIO_PLAY_LEFT_HANDED:
Play_Sound_By_Prefix("013");
break;
case AUDIO_PLAY_GAME_ON:
Play_Sound_By_Prefix("014");
break;
case AUDIO_PLAY_HARDWARE_SETTINGS_PROMPT:
Play_Sound_By_Prefix("015");
break;
case AUDIO_PLAY_GAME_SETTINGS_PROMPT:
Play_Sound_By_Prefix("016");
break;
case AUDIO_PLAY_BONK:
Play_Sound_By_Prefix("017");
break;
case AUDIO_PLAY_NEAR_MISS:
Play_Sound_By_Prefix("018");
break;
case AUDIO_PLAY_PLAYER_ID_PROMPT:
Play_Sound_By_Prefix("019");
break;
case AUDIO_PLAY_TEAM_ID_PROMPT:
Play_Sound_By_Prefix("020");
break;
case AUDIO_PLAY_FRIENDLY_FIRE:
KLOG_WARN(TAG, "\"Friendly Fire\" audio is disabled in this build.");
//Play_Sound_By_Prefix("021");
break;
case AUDIO_PLAY_STARTING_THEME:
Play_Sound_By_Prefix("022");
break;
case AUDIO_PLAY_BOOP:
Play_Sound_By_Prefix("023");
break;
case AUDIO_PLAY_BEEP:
Play_Sound_By_Prefix("024");
break;
case AUDIO_PLAY_REPROGRAMMING:
Play_Sound_By_Prefix("025");
break;
case AUDIO_PLAY_BOMB:
Play_Sound_By_Prefix("026");
break;
case AUDIO_PLAY_GAME_OVER:
Play_Sound_By_Prefix("027");
break;
default:
Play_Audio_File("/spiffs/bad.wav");
break;
}
if (action.Play_To_Completion == true)
{
// Allow some time for the audio to start.
vTaskDelay(100 / portTICK_PERIOD_MS);
while (audio_player_get_state() != AUDIO_PLAYER_STATE_IDLE)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
KEvent_T command_received_event = {.ID = KEVENT_AUDIO_COMPLETED, .Data = (void *)action.ID};
Post_KEvent(&command_received_event);
}
}
}
}

View file

@ -0,0 +1,4 @@
#include "esp_err.h"
void Initialize_Audio(void);
esp_err_t Play_WAV_File(char * filename);

276
components/BLE/BLE.c Normal file
View file

@ -0,0 +1,276 @@
#include <SystemK.h>
#include "esp_log.h"
/* BLE */
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
static const char *TAG = "BLE";
static bool Host_And_Controller_Synced = false;
static bool Is_Scanning_And_Advertising = false;
static uint8_t Advertising_Data[BLE_KTAG_PACKET_TOTAL_SIZE] = {0x1E, 0xFF, 0xFF, 0xFF, 'K', 'T', 'a', 'g'};
// Forward declarations of NimBLE functions.
void ble_store_config_init(void);
static int ble_gap_event(struct ble_gap_event *event, void *arg);
/**
* Initiates the GAP general discovery procedure.
*/
static void ble_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0)
{
KLOG_ERROR(TAG, "Error determining address type; rc=%d", rc);
return;
}
/* Tell the controller not to filter duplicates; we want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 0;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = BLE_GAP_SCAN_ITVL_MS(40);
disc_params.window = BLE_GAP_SCAN_WIN_MS(30);
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, ble_gap_event, NULL);
if (rc != 0)
{
KLOG_ERROR(TAG, "Error initiating GAP discovery procedure; rc=%d", rc);
}
}
static void ble_advertise(void)
{
struct ble_gap_adv_params adv_params;
int rc = ble_gap_adv_set_data((const uint8_t *)&Advertising_Data, sizeof(Advertising_Data));
if (rc != 0)
{
KLOG_ERROR(TAG, "Error setting advertisement data; rc=%d", rc);
return;
}
/* Begin advertising. */
memset(&adv_params, 0, sizeof adv_params);
adv_params.conn_mode = BLE_GAP_CONN_MODE_NON;
adv_params.disc_mode = BLE_GAP_DISC_MODE_NON;
// N.B. High duty-cycle mode requires modification to NimBLE, or you will get an "Error enabling advertisement; rc=530"
// adv_params.high_duty_cycle = 1;
// adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(20);
// adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(25);
adv_params.itvl_min = BLE_GAP_ADV_ITVL_MS(32);
adv_params.itvl_max = BLE_GAP_ADV_ITVL_MS(37);
rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER,
&adv_params, ble_gap_event, NULL);
if (rc != 0)
{
KLOG_ERROR(TAG, "Error enabling advertisement; rc=%d", rc);
return;
}
}
// https://mynewt.apache.org/latest/network/ble_setup/ble_sync_cb.html#reset
static void ble_on_reset(int reason)
{
KLOG_ERROR(TAG, "Resetting state for reason %d.", reason);
Host_And_Controller_Synced = false;
}
// https://mynewt.apache.org/latest/network/ble_setup/ble_sync_cb.html#sync
static void ble_on_sync(void)
{
KLOG_INFO(TAG, "Host and controller synchronized.");
/* Make sure we have proper identity address set (public preferred) */
int rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
Host_And_Controller_Synced = true;
}
/**
* The NimBLE host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that is
* established. KTag uses the same callback for all connections.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by KTag.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_hs_adv_fields fields;
int rc;
switch (event->type)
{
case BLE_GAP_EVENT_DISC:
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0)
{
return 0;
}
BLE_Packet_T *packet = BLE_DecodeKTagPacket(event->disc.data,
event->disc.length_data,
&(event->disc.addr.val[0]),
event->disc.rssi);
if (packet != NULL)
{
if (packet->Generic.type == BLE_PACKET_TYPE_CONSOLE)
{
packet->Console.console_data[BLE_KTAG_PACKET_DATA_SIZE - 1] = '\0';
HW_Execute_Console_Command(packet->Console.console_data);
}
else
{
KEvent_T packet_received_event = {.ID = KEVENT_BLE_PACKET_RECEIVED, .Data = packet};
Post_KEvent(&packet_received_event);
}
}
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
KLOG_INFO(TAG, "Advertise complete; reason=%d", event->adv_complete.reason);
ble_advertise();
return 0;
default:
return 0;
}
}
void ble_host_task(void *param)
{
KLOG_INFO(TAG, "BLE Host Task Started");
// This function will return only after nimble_port_stop() is called:
nimble_port_run();
nimble_port_freertos_deinit();
}
void Initialize_BLE(void)
{
KLOG_INFO(TAG, "Initializing BLE...");
esp_err_t ret = nimble_port_init();
if (ret != ESP_OK)
{
KLOG_ERROR(TAG, "Failed to initialize NimBLE for reason %d.", ret);
return;
}
/* Configure the host. */
ble_hs_cfg.reset_cb = ble_on_reset;
ble_hs_cfg.sync_cb = ble_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_store_config_init();
nimble_port_freertos_init(ble_host_task);
}
void Disable_BLE(void)
{
KLOG_INFO(TAG, "Disabling BLE...");
esp_err_t ret = nimble_port_deinit();
if (ret != ESP_OK)
{
KLOG_ERROR(TAG, "Failed to deinitialize NimBLE for reason %d.", ret);
}
else
{
KLOG_INFO(TAG, "NimBLE deinitialized--BLE disabled.");
}
}
SystemKResult_T BLE_GetMyAddress(uint8_t *BD_ADDR)
{
/* Try to load a public address. */
int result = ble_hs_id_copy_addr(BLE_ADDR_PUBLIC, BD_ADDR, NULL);
if (result == BLE_HS_ENOADDR)
{
return SYSTEMK_RESULT_NOT_IMPLEMENTED;
}
else
{
return SYSTEMK_RESULT_SUCCESS;
}
}
SystemKResult_T BLE_SetAdvertisingData(BLE_AdvertisingData_T *data)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
if (data->length > BLE_MAX_ADVERTISING_BYTES)
{
result = SYSTEMK_RESULT_TOO_MANY_DATA;
}
else if (data->length < BLE_KTAG_PACKET_TOTAL_SIZE)
{
result = SYSTEMK_RESULT_TOO_FEW_DATA;
}
else
{
memcpy(Advertising_Data, data, BLE_KTAG_PACKET_TOTAL_SIZE);
}
if (Is_Scanning_And_Advertising == true)
{
// Restart advertising to put the new data into the advertisement.
ble_gap_adv_stop();
ble_advertise();
}
return result;
}
SystemKResult_T BLE_ScanAndAdvertise(void)
{
SystemKResult_T result = SYSTEMK_RESULT_NOT_READY;
if (Host_And_Controller_Synced == true)
{
if (Is_Scanning_And_Advertising == false)
{
ble_scan();
ble_advertise();
Is_Scanning_And_Advertising = true;
}
result = SYSTEMK_RESULT_SUCCESS;
}
return result;
}

4
components/BLE/BLE.h Normal file
View file

@ -0,0 +1,4 @@
#include "esp_err.h"
void Initialize_BLE(void);
void Disable_BLE(void);

View file

@ -0,0 +1,10 @@
idf_component_register(
SRCS
"BLE.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"driver"
"bt"
)

View file

@ -0,0 +1,9 @@
idf_component_register(
SRCS
"IR.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"driver"
)

362
components/IR/IR.c Normal file
View file

@ -0,0 +1,362 @@
#include <SystemK.h>
#include <driver/gpio.h>
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#define IR_RESOLUTION_HZ (1 * 1000 * 1000) // 1MHz resolution, 1 tick = 1us
#define IR_TX_PIN GPIO_NUM_6
#define IR_TX_RIGHT_ENABLE GPIO_NUM_7
#define IR_TX_LEFT_ENABLE GPIO_NUM_5
#define IR_RX_LEFT_PIN GPIO_NUM_2
#define IR_RX_FORWARD_PIN GPIO_NUM_15
#define IR_RX_RIGHT_PIN GPIO_NUM_42
static const char *TAG = "IR";
static rmt_channel_handle_t Tx_Channel = NULL;
static gpio_config_t tx_right_enable_gpio_config = {
.pin_bit_mask = (1ULL << IR_TX_RIGHT_ENABLE),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
static gpio_config_t tx_left_enable_gpio_config = {
.pin_bit_mask = (1ULL << IR_TX_LEFT_ENABLE),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
// Create simple encoder
rmt_copy_encoder_config_t Copy_Encoder_Config;
rmt_encoder_handle_t Tx_Encoder;
rmt_transmit_config_t Tx_Config = {
.loop_count = 0, // no transfer loop
};
rmt_receive_config_t Rx_Config = {
.signal_range_min_ns = 5 * 1000, // A pulse whose width is smaller than this threshold will be treated as glitch and ignored.
.signal_range_max_ns = 20 * 1000 * 1000, // RMT will stop receiving if one symbol level has kept longer than this value.
.flags.en_partial_rx = false};
static rmt_channel_handle_t Left_Rx_Channel = NULL;
static rmt_channel_handle_t Forward_Rx_Channel = NULL;
static rmt_channel_handle_t Right_Rx_Channel = NULL;
static QueueHandle_t Receive_Queue;
static TimedPulseTrain_T Left_Rxd_RMT_Data;
static TimedPulseTrain_T Forward_Rxd_RMT_Data;
static TimedPulseTrain_T Right_Rxd_RMT_Data;
#define IR_RX_STACK_SIZE 4096
static StaticTask_t xTaskBuffer;
static StackType_t xStack[IR_RX_STACK_SIZE];
static TaskHandle_t IR_Rx_Task_Handle;
static TagPacket_T Shot_Packet;
typedef struct
{
uint8_t count;
TagSensorLocation_T location;
} RxNotification_T;
static bool RMT_Rx_Left_Done_Callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
RxNotification_T notice;
notice.count = edata->num_symbols * 2;
notice.location = TAG_SENSOR_LEFT;
xQueueSendFromISR(Receive_Queue, &notice, &xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken;
}
static bool RMT_Rx_Forward_Done_Callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
RxNotification_T notice;
notice.count = edata->num_symbols * 2;
notice.location = TAG_SENSOR_FORWARD;
xQueueSendFromISR(Receive_Queue, &notice, &xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken;
}
static bool RMT_Rx_Right_Done_Callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
RxNotification_T notice;
notice.count = edata->num_symbols * 2;
notice.location = TAG_SENSOR_RIGHT;
xQueueSendFromISR(Receive_Queue, &notice, &xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken;
}
static const rmt_rx_event_callbacks_t left_cbs = {
.on_recv_done = RMT_Rx_Left_Done_Callback,
};
static const rmt_rx_event_callbacks_t forward_cbs = {
.on_recv_done = RMT_Rx_Forward_Done_Callback,
};
static const rmt_rx_event_callbacks_t right_cbs = {
.on_recv_done = RMT_Rx_Right_Done_Callback,
};
static void IR_Receive_Task(void *param)
{
KLOG_INFO(TAG, "IR Receive Task Started");
while (true)
{
RxNotification_T notice;
DecodedPacket_T *result = NULL;
if (xQueueReceive(Receive_Queue, &notice, portMAX_DELAY) == pdPASS)
{
if (notice.location == TAG_SENSOR_FORWARD)
{
KLOG_INFO(TAG, "TAG_SENSOR_FORWARD Rx'd");
Forward_Rxd_RMT_Data.count = notice.count;
result = PROTOCOLS_MaybeDecodePacket(&Forward_Rxd_RMT_Data);
}
else if (notice.location == TAG_SENSOR_LEFT)
{
KLOG_INFO(TAG, "TAG_SENSOR_LEFT Rx'd");
Left_Rxd_RMT_Data.count = notice.count;
result = PROTOCOLS_MaybeDecodePacket(&Left_Rxd_RMT_Data);
}
else if (notice.location == TAG_SENSOR_RIGHT)
{
KLOG_INFO(TAG, "TAG_SENSOR_RIGHT Rx'd");
Right_Rxd_RMT_Data.count = notice.count;
result = PROTOCOLS_MaybeDecodePacket(&Right_Rxd_RMT_Data);
}
if (result != NULL)
{
if (result->Generic.type == DECODED_PACKET_TYPE_TAG_RECEIVED)
{
KEvent_T tag_received_event = {.ID = KEVENT_TAG_RECEIVED, .Data = result};
Post_KEvent(&tag_received_event);
}
else if (result->Generic.type == DECODED_PACKET_TYPE_COMMAND_RECEIVED)
{
KEvent_T command_received_event = {.ID = KEVENT_COMMAND_RECEIVED, .Data = result};
Post_KEvent(&command_received_event);
}
}
else
{
KEvent_T near_miss_event = {.ID = KEVENT_NEAR_MISS, .Data = NULL};
Post_KEvent(&near_miss_event);
}
// Start receiving again.
// We need to reset all three channels for some reason--why?
{
ESP_ERROR_CHECK(rmt_disable(Forward_Rx_Channel));
ESP_ERROR_CHECK(rmt_enable(Forward_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Forward_Rx_Channel, &Forward_Rxd_RMT_Data, sizeof(Forward_Rxd_RMT_Data.pulsetrain), &Rx_Config));
}
{
ESP_ERROR_CHECK(rmt_disable(Left_Rx_Channel));
ESP_ERROR_CHECK(rmt_enable(Left_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Left_Rx_Channel, &Left_Rxd_RMT_Data, sizeof(Left_Rxd_RMT_Data.pulsetrain), &Rx_Config));
}
{
ESP_ERROR_CHECK(rmt_disable(Right_Rx_Channel));
ESP_ERROR_CHECK(rmt_enable(Right_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Right_Rx_Channel, &Right_Rxd_RMT_Data, sizeof(Right_Rxd_RMT_Data.pulsetrain), &Rx_Config));
}
}
}
}
static void Initialize_Receive_Task(void)
{
IR_Rx_Task_Handle = xTaskCreateStaticPinnedToCore(
IR_Receive_Task, // Function that implements the task.
"IR Rx", // Text name for the task.
IR_RX_STACK_SIZE, // Stack size in words, not bytes.
0, // Parameter passed into the task.
tskIDLE_PRIORITY + 1, // Priority at which the task is created.
xStack, // Array to use as the task's stack.
&xTaskBuffer, // Variable to hold the task's data structure.
APP_CPU_NUM); // Core where the task should run.
if (IR_Rx_Task_Handle == NULL)
{
KLOG_ERROR(TAG, "Failed to create IR Receive task!");
}
}
void Initialize_IR(SemaphoreHandle_t init_complete)
{
KLOG_INFO(TAG, "Initializing IR...");
KLOG_INFO(TAG, "Creating RMT TX channel...");
rmt_tx_channel_config_t tx_channel_cfg = {
.gpio_num = IR_TX_PIN, // GPIO number used by RMT TX channel.
.clk_src = RMT_CLK_SRC_XTAL, // Clock source of RMT TX channel, channels in the same group must use the same clock source
.resolution_hz = IR_RESOLUTION_HZ, // Channel clock resolution, in Hz.
.mem_block_symbols = 48, // If DMA is not used, this field controls the size of the dedicated memory block owned by the channel.
// THIS IS WRONG IN THE DOCS! See https://github.com/espressif/esp-idf/issues/12084#issuecomment-1679881770.
.trans_queue_depth = 1, // Depth of the internal transaction queue.
.flags.invert_out = false, // Should the signal be inverted before sending it to the GPIO?
.flags.with_dma = true, // Should this channel use the DMA backend?
.flags.io_loop_back = false, // Should the signal output from the GPIO be fed to the input path as well?
.flags.io_od_mode = false, // Should the GPIO be configured in open-drain mode?
.intr_priority = 1 // RMT interrupt priority. If set to 0 , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2, or 3).
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &Tx_Channel));
KLOG_INFO(TAG, "Modulate carrier to TX channel");
rmt_carrier_config_t carrier_cfg = {
.duty_cycle = 0.30, // 30% duty cycle
.frequency_hz = 38000, // 38KHz
};
ESP_ERROR_CHECK(rmt_apply_carrier(Tx_Channel, &carrier_cfg));
ESP_ERROR_CHECK(rmt_enable(Tx_Channel));
ESP_ERROR_CHECK(gpio_config(&tx_right_enable_gpio_config));
KLOG_INFO(TAG, "Initialized right IR Tx enable as GPIO[%d].", IR_TX_RIGHT_ENABLE);
ESP_ERROR_CHECK(gpio_config(&tx_left_enable_gpio_config));
KLOG_INFO(TAG, "Initialized left IR Tx enable as GPIO[%d].", IR_TX_LEFT_ENABLE);
ESP_ERROR_CHECK(rmt_new_copy_encoder(&Copy_Encoder_Config, &Tx_Encoder));
KLOG_INFO(TAG, "Creating RMT Rx channels...");
rmt_rx_channel_config_t rx_left_channel_cfg = {
.gpio_num = IR_RX_LEFT_PIN, // GPIO number used by RMT RX channel. Set to -1 if unused.
.clk_src = RMT_CLK_SRC_XTAL, // Clock source of RMT RX channel; channels in the same group must use the same clock source.
.resolution_hz = IR_RESOLUTION_HZ, // Channel clock resolution, in Hz.
.mem_block_symbols = 48, // Size of memory block, in number of `rmt_symbol_word_t`, must be an even.
// In the DMA mode, this field controls the DMA buffer size, it can be set to a large value (e.g. 1024);
// In the normal mode, this field controls the number of RMT memory block that will be used by the channel.
.flags.invert_in = true, // Should the incoming signal be inverted before processing?
.flags.with_dma = false, // Should this channel use the DMA backend?
.flags.io_loop_back = false, // Should the signal output from the GPIO be fed to the input path as well?
.intr_priority = 1 // RMT interrupt priority. If set to 0 , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2, or 3).
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_left_channel_cfg, &Left_Rx_Channel));
rmt_rx_channel_config_t rx_forward_channel_cfg = {
.gpio_num = IR_RX_FORWARD_PIN, // GPIO number used by RMT RX channel. Set to -1 if unused.
.clk_src = RMT_CLK_SRC_XTAL, // Clock source of RMT RX channel; channels in the same group must use the same clock source.
.resolution_hz = IR_RESOLUTION_HZ, // Channel clock resolution, in Hz.
.mem_block_symbols = 48, // Size of memory block, in number of `rmt_symbol_word_t`, must be an even.
// In the DMA mode, this field controls the DMA buffer size, it can be set to a large value (e.g. 1024);
// In the normal mode, this field controls the number of RMT memory block that will be used by the channel.
.flags.invert_in = true, // Should the incoming signal be inverted before processing?
.flags.with_dma = false, // Should this channel use the DMA backend?
.flags.io_loop_back = false, // Should the signal output from the GPIO be fed to the input path as well?
.intr_priority = 1 // RMT interrupt priority. If set to 0 , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2, or 3).
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_forward_channel_cfg, &Forward_Rx_Channel));
rmt_rx_channel_config_t rx_right_channel_cfg = {
.gpio_num = IR_RX_RIGHT_PIN, // GPIO number used by RMT RX channel. Set to -1 if unused.
.clk_src = RMT_CLK_SRC_XTAL, // Clock source of RMT RX channel; channels in the same group must use the same clock source.
.resolution_hz = IR_RESOLUTION_HZ, // Channel clock resolution, in Hz.
.mem_block_symbols = 48, // Size of memory block, in number of `rmt_symbol_word_t`, must be an even.
// In the DMA mode, this field controls the DMA buffer size, it can be set to a large value (e.g. 1024);
// In the normal mode, this field controls the number of RMT memory block that will be used by the channel.
.flags.invert_in = true, // Should the incoming signal be inverted before processing?
.flags.with_dma = false, // Should this channel use the DMA backend?
.flags.io_loop_back = false, // Should the signal output from the GPIO be fed to the input path as well?
.intr_priority = 1 // RMT interrupt priority. If set to 0 , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2, or 3).
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_right_channel_cfg, &Right_Rx_Channel));
ESP_LOGI(TAG, "register RX done callbacks");
Receive_Queue = xQueueCreate(1, sizeof(RxNotification_T));
assert(Receive_Queue);
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(Left_Rx_Channel, &left_cbs, 0));
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(Forward_Rx_Channel, &forward_cbs, 0));
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(Right_Rx_Channel, &right_cbs, 0));
Initialize_Receive_Task();
Left_Rxd_RMT_Data.receiver = TAG_SENSOR_LEFT;
Forward_Rxd_RMT_Data.receiver = TAG_SENSOR_FORWARD;
Right_Rxd_RMT_Data.receiver = TAG_SENSOR_RIGHT;
ESP_ERROR_CHECK(rmt_enable(Left_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Left_Rx_Channel, &Left_Rxd_RMT_Data, sizeof(Left_Rxd_RMT_Data.pulsetrain), &Rx_Config));
ESP_ERROR_CHECK(rmt_enable(Forward_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Forward_Rx_Channel, &Forward_Rxd_RMT_Data, sizeof(Forward_Rxd_RMT_Data.pulsetrain), &Rx_Config));
ESP_ERROR_CHECK(rmt_enable(Right_Rx_Channel));
ESP_ERROR_CHECK(rmt_receive(Right_Rx_Channel, &Right_Rxd_RMT_Data, sizeof(Right_Rxd_RMT_Data.pulsetrain), &Rx_Config));
xSemaphoreGive(init_complete);
}
static inline void PrintPulseTrainToConsole(TimedPulseTrain_T *train)
{
for (uint_fast16_t i = 0; i < train->count; i += 2)
{
KLOG_INFO(TAG, "%2d: (%d, %4d) (%d, %4d)", i + 1, train->bitstream[i].symbol, train->bitstream[i].duration, train->bitstream[i + 1].symbol, train->bitstream[i + 1].duration);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
SystemKResult_T Prepare_Tag(void)
{
TimedPulseTrain_T *Shot_Buffer;
uint8_t team_ID;
uint8_t player_ID;
uint8_t weapon_ID;
(void)SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &team_ID);
(void)SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &player_ID);
(void)SETTINGS_get_uint8_t(SYSTEMK_SETTING_WEAPONID, &weapon_ID);
Weapon_t weapon = GetWeaponFromID(weapon_ID);
Shot_Packet.player_ID = player_ID;
Shot_Packet.team_ID = team_ID;
Shot_Packet.color = (uint32_t)PROTOCOLS_GetColor(weapon.Protocol, team_ID, player_ID);
Shot_Packet.protocol = weapon.Protocol;
Shot_Packet.damage = weapon.Damage_Per_Shot;
Shot_Buffer = PROTOCOLS_EncodePacket(&Shot_Packet);
KLOG_INFO(TAG, "Tag prepared (%u pulses):", Shot_Buffer->count);
PrintPulseTrainToConsole(Shot_Buffer);
gpio_set_level(IR_TX_RIGHT_ENABLE, 0);
gpio_set_level(IR_TX_LEFT_ENABLE, 0);
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T Send_Tag(void)
{
TimedPulseTrain_T *Shot_Buffer;
Shot_Buffer = PROTOCOLS_EncodePacket(&Shot_Packet);
ESP_ERROR_CHECK(rmt_transmit(Tx_Channel, Tx_Encoder, Shot_Buffer->pulsetrain, sizeof(rmt_symbol_word_t) * Shot_Buffer->count, &Tx_Config));
KEvent_T tag_sent_event = {.ID = KEVENT_TAG_SENT, .Data = (void *)0x00};
Post_KEvent(&tag_sent_event);
return SYSTEMK_RESULT_SUCCESS;
}

3
components/IR/IR.h Normal file
View file

@ -0,0 +1,3 @@
#include "esp_err.h"
void Initialize_IR(SemaphoreHandle_t init_complete);

View file

@ -0,0 +1,38 @@
## 2.5.0
- Enabled support for IDF4.4 and above
- with RMT backend only
- Added API `led_strip_set_pixel_hsv`
## 2.4.0
- Support configurable SPI mode to control leds
- recommend enabling DMA when using SPI mode
## 2.3.0
- Support configurable RMT channel size by setting `mem_block_symbols`
## 2.2.0
- Support for 4 components RGBW leds (SK6812):
- in led_strip_config_t new fields
led_pixel_format, controlling byte format (LED_PIXEL_FORMAT_GRB, LED_PIXEL_FORMAT_GRBW)
led_model, used to configure bit timing (LED_MODEL_WS2812, LED_MODEL_SK6812)
- new API led_strip_set_pixel_rgbw
- new interface type set_pixel_rgbw
## 2.1.0
- Support DMA feature, which offloads the CPU by a lot when it comes to drive a bunch of LEDs
- Support various RMT clock sources
- Acquire and release the power management lock before and after each refresh
- New driver flag: `invert_out` which can invert the led control signal by hardware
## 2.0.0
- Reimplemented the driver using the new RMT driver (`driver/rmt_tx.h`)
## 1.0.0
- Initial driver version, based on the legacy RMT driver (`driver/rmt.h`)

View file

@ -0,0 +1,22 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
set(srcs "src/led_strip_api.c" "src/muxed_led_strip_api.c")
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
if(CONFIG_SOC_RMT_SUPPORTED)
list(APPEND srcs "src/led_strip_rmt_dev.c" "src/muxed_led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c")
endif()
else()
list(APPEND srcs "src/led_strip_rmt_dev_idf4.c")
endif()
# the SPI backend driver relies on something that was added in IDF 5.1
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.1")
if(CONFIG_SOC_GPSPI_SUPPORTED)
list(APPEND srcs "src/led_strip_spi_dev.c")
endif()
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include" "interface"
REQUIRES "driver")

View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,97 @@
# LED Strip Driver
[![Component Registry](https://components.espressif.com/components/espressif/led_strip/badge.svg)](https://components.espressif.com/components/espressif/led_strip)
This driver is designed for addressable LEDs like [WS2812](http://www.world-semi.com/Certifications/WS2812B.html), where each LED is controlled by a single data line.
## Backend Controllers
### The [RMT](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html) Peripheral
This is the most economical way to drive the LEDs because it only consumes one RMT channel, leaving other channels free to use. However, the memory usage increases dramatically with the number of LEDs. If the RMT hardware can't be assist by DMA, the driver will going into interrupt very frequently, thus result in a high CPU usage. What's worse, if the RMT interrupt is delayed or not serviced in time (e.g. if Wi-Fi interrupt happens on the same CPU core), the RMT transaction will be corrupted and the LEDs will display incorrect colors. If you want to use RMT to drive a large number of LEDs, you'd better to enable the DMA feature if possible [^1].
#### Allocate LED Strip Object with RMT Backend
```c
#define BLINK_GPIO 0
led_strip_handle_t led_strip;
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal (useful when your hardware has a level inverter)
};
led_strip_rmt_config_t rmt_config = {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.rmt_channel = 0,
#else
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = 10 * 1000 * 1000, // 10MHz
.flags.with_dma = false, // whether to enable the DMA feature
#endif
};
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
```
You can create multiple LED strip objects with different GPIOs and pixel numbers. The backend driver will automatically allocate the RMT channel for you if there is more available.
### The [SPI](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html) Peripheral
SPI peripheral can also be used to generate the timing required by the LED strip. However this backend is not as economical as the RMT one, because it will take up the whole **bus**, unlike the RMT just takes one **channel**. You **CANT** connect other devices to the same SPI bus if it's been used by the led_strip, because the led_strip doesn't have the concept of "Chip Select".
Please note, the SPI backend has a dependency of **ESP-IDF >= 5.1**
#### Allocate LED Strip Object with SPI Backend
```c
#define BLINK_GPIO 0
led_strip_handle_t led_strip;
/* LED strip initialization with the GPIO and pixels number*/
led_strip_config_t strip_config = {
.strip_gpio_num = BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = 1, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal (useful when your hardware has a level inverter)
};
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.flags.with_dma = true, // Using DMA can improve performance and help drive more LEDs
.spi_bus = SPI2_HOST, // SPI bus ID
};
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
```
The number of LED strip objects can be created depends on how many free SPI buses are free to use in your project.
## FAQ
* Which led_strip backend should I choose?
* It depends on your application requirement and target chip's ability.
```mermaid
flowchart LR
A{Is RMT supported?}
A --> |No| B[SPI backend]
B --> C{Does the led strip has \n a larger number of LEDs?}
C --> |No| D[Don't have to enable the DMA of the backend]
C --> |Yes| E[Enable the DMA of the backend]
A --> |Yes| F{Does the led strip has \n a larger number of LEDs?}
F --> |Yes| G{Does RMT support DMA?}
G --> |Yes| E
G --> |No| B
F --> |No| H[RMT backend] --> D
```
* How to set the brightness of the LED strip?
* You can tune the brightness by scaling the value of each R-G-B element with a **same** factor. But pay attention to the overflow of the value.
[^1]: The RMT DMA feature is not available on all ESP chips. Please check the data sheet before using it.

View file

@ -0,0 +1,454 @@
# API Reference
## Header files
- [include/led_strip.h](#file-includeled_striph)
- [include/led_strip_rmt.h](#file-includeled_strip_rmth)
- [include/led_strip_spi.h](#file-includeled_strip_spih)
- [include/led_strip_types.h](#file-includeled_strip_typesh)
- [interface/led_strip_interface.h](#file-interfaceled_strip_interfaceh)
## File include/led_strip.h
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_clear**](#function-led_strip_clear) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip) <br>_Clear LED strip (turn off all LEDs)_ |
| esp\_err\_t | [**led\_strip\_del**](#function-led_strip_del) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip) <br>_Free LED strip resources._ |
| esp\_err\_t | [**led\_strip\_refresh**](#function-led_strip_refresh) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip) <br>_Refresh memory colors to LEDs._ |
| esp\_err\_t | [**led\_strip\_set\_pixel**](#function-led_strip_set_pixel) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip, uint32\_t index, uint32\_t red, uint32\_t green, uint32\_t blue) <br>_Set RGB for a specific pixel._ |
| esp\_err\_t | [**led\_strip\_set\_pixel\_hsv**](#function-led_strip_set_pixel_hsv) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip, uint32\_t index, uint16\_t hue, uint8\_t saturation, uint8\_t value) <br>_Set HSV for a specific pixel._ |
| esp\_err\_t | [**led\_strip\_set\_pixel\_rgbw**](#function-led_strip_set_pixel_rgbw) ([**led\_strip\_handle\_t**](#struct-led_strip_t) strip, uint32\_t index, uint32\_t red, uint32\_t green, uint32\_t blue, uint32\_t white) <br>_Set RGBW for a specific pixel._ |
## Functions Documentation
### function `led_strip_clear`
_Clear LED strip (turn off all LEDs)_
```c
esp_err_t led_strip_clear (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Clear LEDs successfully
- ESP\_FAIL: Clear LEDs failed because some other error occurred
### function `led_strip_del`
_Free LED strip resources._
```c
esp_err_t led_strip_del (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Free resources successfully
- ESP\_FAIL: Free resources failed because error occurred
### function `led_strip_refresh`
_Refresh memory colors to LEDs._
```c
esp_err_t led_strip_refresh (
led_strip_handle_t strip
)
```
**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Refresh successfully
- ESP\_FAIL: Refresh failed because some other error occurred
**Note:**
: After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
### function `led_strip_set_pixel`
_Set RGB for a specific pixel._
```c
esp_err_t led_strip_set_pixel (
led_strip_handle_t strip,
uint32_t index,
uint32_t red,
uint32_t green,
uint32_t blue
)
```
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
**Returns:**
- ESP\_OK: Set RGB for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGB for a specific pixel failed because of invalid parameters
- ESP\_FAIL: Set RGB for a specific pixel failed because other error occurred
### function `led_strip_set_pixel_hsv`
_Set HSV for a specific pixel._
```c
esp_err_t led_strip_set_pixel_hsv (
led_strip_handle_t strip,
uint32_t index,
uint16_t hue,
uint8_t saturation,
uint8_t value
)
```
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `hue` hue part of color (0 - 360)
- `saturation` saturation part of color (0 - 255)
- `value` value part of color (0 - 255)
**Returns:**
- ESP\_OK: Set HSV color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set HSV color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set HSV color for a specific pixel failed because other error occurred
### function `led_strip_set_pixel_rgbw`
_Set RGBW for a specific pixel._
```c
esp_err_t led_strip_set_pixel_rgbw (
led_strip_handle_t strip,
uint32_t index,
uint32_t red,
uint32_t green,
uint32_t blue,
uint32_t white
)
```
**Note:**
Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
**Note:**
Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
- `white` separate white component
**Returns:**
- ESP\_OK: Set RGBW color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set RGBW color for a specific pixel failed because other error occurred
## File include/led_strip_rmt.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_rmt\_config\_t**](#struct-led_strip_rmt_config_t) <br>_LED Strip RMT specific configuration._ |
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_new\_rmt\_device**](#function-led_strip_new_rmt_device) (const [**led\_strip\_config\_t**](#struct-led_strip_config_t) \*led\_config, const [**led\_strip\_rmt\_config\_t**](#struct-led_strip_rmt_config_t) \*rmt\_config, [**led\_strip\_handle\_t**](#struct-led_strip_t) \*ret\_strip) <br>_Create LED strip based on RMT TX channel._ |
## Structures and Types Documentation
### struct `led_strip_rmt_config_t`
_LED Strip RMT specific configuration._
Variables:
- rmt\_clock\_source\_t clk_src <br>RMT clock source
- struct [**led\_strip\_rmt\_config\_t**](#struct-led_strip_rmt_config_t) flags <br>Extra driver flags
- size\_t mem_block_symbols <br>How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size.
- uint32\_t resolution_hz <br>RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied
- uint32\_t with_dma <br>Use DMA to transmit data
## Functions Documentation
### function `led_strip_new_rmt_device`
_Create LED strip based on RMT TX channel._
```c
esp_err_t led_strip_new_rmt_device (
const led_strip_config_t *led_config,
const led_strip_rmt_config_t *rmt_config,
led_strip_handle_t *ret_strip
)
```
**Parameters:**
- `led_config` LED strip configuration
- `rmt_config` RMT specific configuration
- `ret_strip` Returned LED strip handle
**Returns:**
- ESP\_OK: create LED strip handle successfully
- ESP\_ERR\_INVALID\_ARG: create LED strip handle failed because of invalid argument
- ESP\_ERR\_NO\_MEM: create LED strip handle failed because of out of memory
- ESP\_FAIL: create LED strip handle failed because some other error
## File include/led_strip_spi.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_spi\_config\_t**](#struct-led_strip_spi_config_t) <br>_LED Strip SPI specific configuration._ |
## Functions
| Type | Name |
| ---: | :--- |
| esp\_err\_t | [**led\_strip\_new\_spi\_device**](#function-led_strip_new_spi_device) (const [**led\_strip\_config\_t**](#struct-led_strip_config_t) \*led\_config, const [**led\_strip\_spi\_config\_t**](#struct-led_strip_spi_config_t) \*spi\_config, [**led\_strip\_handle\_t**](#struct-led_strip_t) \*ret\_strip) <br>_Create LED strip based on SPI MOSI channel._ |
## Structures and Types Documentation
### struct `led_strip_spi_config_t`
_LED Strip SPI specific configuration._
Variables:
- spi\_clock\_source\_t clk_src <br>SPI clock source
- struct [**led\_strip\_spi\_config\_t**](#struct-led_strip_spi_config_t) flags <br>Extra driver flags
- spi\_host\_device\_t spi_bus <br>SPI bus ID. Which buses are available depends on the specific chip
- uint32\_t with_dma <br>Use DMA to transmit data
## Functions Documentation
### function `led_strip_new_spi_device`
_Create LED strip based on SPI MOSI channel._
```c
esp_err_t led_strip_new_spi_device (
const led_strip_config_t *led_config,
const led_strip_spi_config_t *spi_config,
led_strip_handle_t *ret_strip
)
```
**Note:**
Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes.
**Parameters:**
- `led_config` LED strip configuration
- `spi_config` SPI specific configuration
- `ret_strip` Returned LED strip handle
**Returns:**
- ESP\_OK: create LED strip handle successfully
- ESP\_ERR\_INVALID\_ARG: create LED strip handle failed because of invalid argument
- ESP\_ERR\_NOT\_SUPPORTED: create LED strip handle failed because of unsupported configuration
- ESP\_ERR\_NO\_MEM: create LED strip handle failed because of out of memory
- ESP\_FAIL: create LED strip handle failed because some other error
## File include/led_strip_types.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| enum | [**led\_model\_t**](#enum-led_model_t) <br>_LED strip model._ |
| enum | [**led\_pixel\_format\_t**](#enum-led_pixel_format_t) <br>_LED strip pixel format._ |
| struct | [**led\_strip\_config\_t**](#struct-led_strip_config_t) <br>_LED Strip Configuration._ |
| typedef struct [**led\_strip\_t**](#struct-led_strip_t) \* | [**led\_strip\_handle\_t**](#typedef-led_strip_handle_t) <br>_LED strip handle._ |
## Structures and Types Documentation
### enum `led_model_t`
_LED strip model._
```c
enum led_model_t {
LED_MODEL_WS2812,
LED_MODEL_SK6812,
LED_MODEL_INVALID
};
```
**Note:**
Different led model may have different timing parameters, so we need to distinguish them.
### enum `led_pixel_format_t`
_LED strip pixel format._
```c
enum led_pixel_format_t {
LED_PIXEL_FORMAT_GRB,
LED_PIXEL_FORMAT_GRBW,
LED_PIXEL_FORMAT_INVALID
};
```
### struct `led_strip_config_t`
_LED Strip Configuration._
Variables:
- struct [**led\_strip\_config\_t**](#struct-led_strip_config_t) flags <br>Extra driver flags
- uint32\_t invert_out <br>Invert output signal
- led\_model\_t led_model <br>LED model
- led\_pixel\_format\_t led_pixel_format <br>LED pixel format
- uint32\_t max_leds <br>Maximum LEDs in a single strip
- int strip_gpio_num <br>GPIO number that used by LED strip
### typedef `led_strip_handle_t`
_LED strip handle._
```c
typedef struct led_strip_t* led_strip_handle_t;
```
## File interface/led_strip_interface.h
## Structures and Types
| Type | Name |
| ---: | :--- |
| struct | [**led\_strip\_t**](#struct-led_strip_t) <br>_LED strip interface definition._ |
| typedef struct [**led\_strip\_t**](#struct-led_strip_t) | [**led\_strip\_t**](#typedef-led_strip_t) <br> |
## Structures and Types Documentation
### struct `led_strip_t`
_LED strip interface definition._
Variables:
- esp\_err\_t(\* clear <br>_Clear LED strip (turn off all LEDs)_<br>**Parameters:**
- `strip` LED strip
- `timeout_ms` timeout value for clearing task
**Returns:**
- ESP\_OK: Clear LEDs successfully
- ESP\_FAIL: Clear LEDs failed because some other error occurred
- esp\_err\_t(\* del <br>_Free LED strip resources._<br>**Parameters:**
- `strip` LED strip
**Returns:**
- ESP\_OK: Free resources successfully
- ESP\_FAIL: Free resources failed because error occurred
- esp\_err\_t(\* refresh <br>_Refresh memory colors to LEDs._<br>**Parameters:**
- `strip` LED strip
- `timeout_ms` timeout value for refreshing task
**Returns:**
- ESP\_OK: Refresh successfully
- ESP\_FAIL: Refresh failed because some other error occurred
**Note:**
: After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
- esp\_err\_t(\* set_pixel <br>_Set RGB for a specific pixel._<br>**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
**Returns:**
- ESP\_OK: Set RGB for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGB for a specific pixel failed because of invalid parameters
- ESP\_FAIL: Set RGB for a specific pixel failed because other error occurred
- esp\_err\_t(\* set_pixel_rgbw <br>_Set RGBW for a specific pixel. Similar to_ `set_pixel`_but also set the white component._<br>**Parameters:**
- `strip` LED strip
- `index` index of pixel to set
- `red` red part of color
- `green` green part of color
- `blue` blue part of color
- `white` separate white component
**Returns:**
- ESP\_OK: Set RGBW color for a specific pixel successfully
- ESP\_ERR\_INVALID\_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
- ESP\_FAIL: Set RGBW color for a specific pixel failed because other error occurred
### typedef `led_strip_t`
```c
typedef struct led_strip_t led_strip_t;
```
Type of LED strip

View file

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_rmt_ws2812)

View file

@ -0,0 +1,31 @@
# LED Strip Example (RMT backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_rmt_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[8]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with RMT backend
I (309) example: Start blinking LED strip
```

View file

@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_rmt_ws2812_main.c"
INCLUDE_DIRS ".")

View file

@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip:
version: '^2'
override_path: '../../../'

View file

@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// GPIO assignment
#define LED_STRIP_BLINK_GPIO 2
// Numbers of the LED in the strip
#define LED_STRIP_LED_NUMBERS 24
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_NUMBERS, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal
};
// LED strip backend configuration: RMT
led_strip_rmt_config_t rmt_config = {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.rmt_channel = 0,
#else
.clk_src = RMT_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.resolution_hz = LED_STRIP_RMT_RES_HZ, // RMT counter clock frequency
.flags.with_dma = false, // DMA feature is available on ESP target like ESP32-S3
#endif
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with RMT backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View file

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_spi_ws2812)

View file

@ -0,0 +1,31 @@
# LED Strip Example (SPI backend + WS2812)
This example demonstrates how to blink the WS2812 LED using the [led_strip](https://components.espressif.com/component/espressif/led_strip) component.
## How to Use Example
### Hardware Required
* A development board with Espressif SoC
* A USB cable for Power supply and programming
* WS2812 LED strip
### Configure the Example
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`. Then assign the proper GPIO in the [source file](main/led_strip_spi_ws2812_main.c). If your led strip has multiple LEDs, don't forget update the number.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```text
I (299) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) example: Created LED strip object with SPI backend
I (309) example: Start blinking LED strip
```

View file

@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_spi_ws2812_main.c"
INCLUDE_DIRS ".")

View file

@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip:
version: '^2.4'
override_path: '../../../'
idf: ">=5.1"

View file

@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led_strip.h"
#include "esp_log.h"
#include "esp_err.h"
// GPIO assignment
#define LED_STRIP_BLINK_GPIO 2
// Numbers of the LED in the strip
#define LED_STRIP_LED_NUMBERS 24
static const char *TAG = "example";
led_strip_handle_t configure_led(void)
{
// LED strip general initialization, according to your led board design
led_strip_config_t strip_config = {
.strip_gpio_num = LED_STRIP_BLINK_GPIO, // The GPIO that connected to the LED strip's data line
.max_leds = LED_STRIP_LED_NUMBERS, // The number of LEDs in the strip,
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal
};
// LED strip backend configuration: SPI
led_strip_spi_config_t spi_config = {
.clk_src = SPI_CLK_SRC_DEFAULT, // different clock source can lead to different power consumption
.flags.with_dma = true, // Using DMA can improve performance and help drive more LEDs
.spi_bus = SPI2_HOST, // SPI bus ID
};
// LED Strip object handle
led_strip_handle_t led_strip;
ESP_ERROR_CHECK(led_strip_new_spi_device(&strip_config, &spi_config, &led_strip));
ESP_LOGI(TAG, "Created LED strip object with SPI backend");
return led_strip;
}
void app_main(void)
{
led_strip_handle_t led_strip = configure_led();
bool led_on_off = false;
ESP_LOGI(TAG, "Start blinking LED strip");
while (1) {
if (led_on_off) {
/* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */
for (int i = 0; i < LED_STRIP_LED_NUMBERS; i++) {
ESP_ERROR_CHECK(led_strip_set_pixel(led_strip, i, 5, 5, 5));
}
/* Refresh the strip to send data */
ESP_ERROR_CHECK(led_strip_refresh(led_strip));
ESP_LOGI(TAG, "LED ON!");
} else {
/* Set all LED off to clear all pixels */
ESP_ERROR_CHECK(led_strip_clear(led_strip));
ESP_LOGI(TAG, "LED OFF!");
}
led_on_off = !led_on_off;
vTaskDelay(pdMS_TO_TICKS(500));
}
}

View file

@ -0,0 +1,6 @@
dependencies:
idf:
version: '>=4.4'
description: Multiplexed Driver for Addressable LED Strip (WS2812, etc)
url: https://github.com/espressif/idf-extra-components/tree/master/led_strip
version: 2.5.3

View file

@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_rmt.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include "led_strip_spi.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel
*
* @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
* @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Set HSV for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param hue: hue part of color (0 - 360)
* @param saturation: saturation part of color (0 - 255)
* @param value: value part of color (0 - 255)
*
* @return
* - ESP_OK: Set HSV color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred
*/
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t led_strip_refresh(led_strip_handle_t strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t led_strip_clear(led_strip_handle_t strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t led_strip_del(led_strip_handle_t strip);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_types.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/rmt_types.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip RMT specific configuration
*/
typedef struct {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
uint8_t rmt_channel; /*!< Specify the channel number, the legacy RMT driver doesn't support channel allocator */
#else // new driver supports specify the clock source and clock resolution
rmt_clock_source_t clk_src; /*!< RMT clock source */
uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */
#endif
size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */
struct {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_rmt_config_t;
/**
* @brief Create LED strip based on RMT TX channel
*
* @param led_config LED strip configuration
* @param rmt_config RMT specific configuration
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "driver/spi_master.h"
#include "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED Strip SPI specific configuration
*/
typedef struct {
spi_clock_source_t clk_src; /*!< SPI clock source */
spi_host_device_t spi_bus; /*!< SPI bus ID. Which buses are available depends on the specific chip */
struct {
uint32_t with_dma: 1; /*!< Use DMA to transmit data */
} flags; /*!< Extra driver flags */
} led_strip_spi_config_t;
/**
* @brief Create LED strip based on SPI MOSI channel
* @note Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes.
*
* @param led_config LED strip configuration
* @param spi_config SPI specific configuration
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NOT_SUPPORTED: create LED strip handle failed because of unsupported configuration
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED strip pixel format
*/
typedef enum {
LED_PIXEL_FORMAT_GRB, /*!< Pixel format: GRB */
LED_PIXEL_FORMAT_GRBW, /*!< Pixel format: GRBW */
LED_PIXEL_FORMAT_INVALID /*!< Invalid pixel format */
} led_pixel_format_t;
/**
* @brief LED strip model
* @note Different led model may have different timing parameters, so we need to distinguish them.
*/
typedef enum {
LED_MODEL_WS2812, /*!< LED strip model: WS2812 */
LED_MODEL_SK6812, /*!< LED strip model: SK6812 */
LED_MODEL_INVALID /*!< Invalid LED strip model */
} led_model_t;
/**
* @brief LED strip handle
*/
typedef struct led_strip_t *led_strip_handle_t;
/**
* @brief LED Strip Configuration
*/
typedef struct {
int strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t max_leds; /*!< Maximum LEDs in a single strip */
led_pixel_format_t led_pixel_format; /*!< LED pixel format */
led_model_t led_model; /*!< LED model */
struct {
uint32_t invert_out: 1; /*!< Invert output signal */
} flags; /*!< Extra driver flags */
} led_strip_config_t;
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "muxed_led_strip_rmt.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include "led_strip_spi.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param channel: channel of pixel to set
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t muxed_led_strip_set_pixel(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel
*
* @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW)
* @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component
*
* @param strip: LED strip
* @param channel: channel of pixel to set
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t muxed_led_strip_set_pixel_rgbw(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Set HSV for a specific pixel
*
* @param strip: LED strip
* @param channel: channel of pixel to set
* @param index: index of pixel to set
* @param hue: hue part of color (0 - 360)
* @param saturation: saturation part of color (0 - 255)
* @param value: value part of color (0 - 255)
*
* @return
* - ESP_OK: Set HSV color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred
*/
esp_err_t muxed_led_strip_set_pixel_hsv(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param channel: which channel to refresh
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t muxed_led_strip_refresh(muxed_led_strip_handle_t strip, uint32_t channel);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param channel: which channel to clear
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t muxed_led_strip_clear(muxed_led_strip_handle_t strip, uint32_t channel);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t muxed_led_strip_del(muxed_led_strip_handle_t strip);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "led_strip_types.h"
#include "muxed_led_strip_types.h"
#include "led_strip_rmt.h"
#include "esp_idf_version.h"
#include <driver/rmt_tx.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "driver/rmt_types.h"
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Create multiplexed LED strip based on RMT TX channel
*
* @param led_config LED strip configuration
* @param rmt_config RMT specific configuration
* @param cbs Group of RMT TX callback functions
* @param ret_strip Returned LED strip handle
* @return
* - ESP_OK: create LED strip handle successfully
* - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument
* - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory
* - ESP_FAIL: create LED strip handle failed because some other error
*/
esp_err_t muxed_led_strip_new_rmt_device(const muxed_led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, const rmt_tx_event_callbacks_t *cbs, muxed_led_strip_handle_t *ret_strip);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Multiplexed LED strip handle
*/
typedef struct muxed_led_strip_t *muxed_led_strip_handle_t;
/**
* @brief LED Strip Configuration
*/
typedef struct {
int strip_gpio_num; /*!< GPIO number that used by LED strip */
uint32_t channels; /*!< Number of multiplexed channels */
uint32_t max_leds; /*!< Maximum LEDs in a single strip */
led_pixel_format_t led_pixel_format; /*!< LED pixel format */
led_model_t led_model; /*!< LED model */
struct {
uint32_t invert_out: 1; /*!< Invert output signal */
} flags; /*!< Extra driver flags */
} muxed_led_strip_config_t;
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */
/**
* @brief LED strip interface definition
*/
struct led_strip_t {
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component
*
* @param strip: LED strip
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel_rgbw)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param timeout_ms: timeout value for refreshing task
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t (*refresh)(led_strip_t *strip);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param timeout_ms: timeout value for clearing task
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(led_strip_t *strip);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t (*del)(led_strip_t *strip);
};
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct muxed_led_strip_t muxed_led_strip_t; /*!< Type of LED strip */
/**
* @brief LED strip interface definition
*/
struct muxed_led_strip_t {
/**
* @brief Set RGB for a specific pixel
*
* @param strip: LED strip
* @param channel: channel of pixel to set
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
*
* @return
* - ESP_OK: Set RGB for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters
* - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel)(muxed_led_strip_t *strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue);
/**
* @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component
*
* @param strip: LED strip
* @param channel: channel of pixel to set
* @param index: index of pixel to set
* @param red: red part of color
* @param green: green part of color
* @param blue: blue part of color
* @param white: separate white component
*
* @return
* - ESP_OK: Set RGBW color for a specific pixel successfully
* - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument
* - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred
*/
esp_err_t (*set_pixel_rgbw)(muxed_led_strip_t *strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white);
/**
* @brief Refresh memory colors to LEDs
*
* @param strip: LED strip
* @param channel: which channel to refresh
*
* @return
* - ESP_OK: Refresh successfully
* - ESP_FAIL: Refresh failed because some other error occurred
*
* @note:
* After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip.
*/
esp_err_t (*refresh)(muxed_led_strip_t *strip, uint32_t channel);
/**
* @brief Clear LED strip (turn off all LEDs)
*
* @param strip: LED strip
* @param channel: which channel to clear
*
* @return
* - ESP_OK: Clear LEDs successfully
* - ESP_FAIL: Clear LEDs failed because some other error occurred
*/
esp_err_t (*clear)(muxed_led_strip_t *strip, uint32_t channel);
/**
* @brief Free LED strip resources
*
* @param strip: LED strip
*
* @return
* - ESP_OK: Free resources successfully
* - ESP_FAIL: Free resources failed because error occurred
*/
esp_err_t (*del)(muxed_led_strip_t *strip);
};
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_check.h"
#include "led_strip.h"
#include "led_strip_interface.h"
static const char *TAG = "led_strip";
esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
uint32_t rgb_max = value;
uint32_t rgb_min = rgb_max * (255 - saturation) / 255.0f;
uint32_t i = hue / 60;
uint32_t diff = hue % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
red = rgb_max;
green = rgb_min + rgb_adj;
blue = rgb_min;
break;
case 1:
red = rgb_max - rgb_adj;
green = rgb_max;
blue = rgb_min;
break;
case 2:
red = rgb_min;
green = rgb_max;
blue = rgb_min + rgb_adj;
break;
case 3:
red = rgb_min;
green = rgb_max - rgb_adj;
blue = rgb_max;
break;
case 4:
red = rgb_min + rgb_adj;
green = rgb_min;
blue = rgb_max;
break;
default:
red = rgb_max;
green = rgb_min;
blue = rgb_max - rgb_adj;
break;
}
return strip->set_pixel(strip, index, red, green, blue);
}
esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel_rgbw(strip, index, red, green, blue, white);
}
esp_err_t led_strip_refresh(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->refresh(strip);
}
esp_err_t led_strip_clear(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->clear(strip);
}
esp_err_t led_strip_del(led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->del(strip);
}

View file

@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt_tx.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "led_strip_rmt_encoder.h"
#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution
#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4
// the memory size of each RMT channel, in words (4 bytes)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
#else
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
#endif
static const char *TAG = "led_strip_rmt";
typedef struct {
led_strip_t base;
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t pixel_buf[];
} led_strip_rmt_obj;
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
uint32_t start = index * rmt_strip->bytes_per_pixel;
// In thr order of GRB, as LED strip like WS2812 sends out pixels in this order
rmt_strip->pixel_buf[start + 0] = green & 0xFF;
rmt_strip->pixel_buf[start + 1] = red & 0xFF;
rmt_strip->pixel_buf[start + 2] = blue & 0xFF;
if (rmt_strip->bytes_per_pixel > 3) {
rmt_strip->pixel_buf[start + 3] = 0;
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(rmt_strip->bytes_per_pixel == 4, ESP_ERR_INVALID_ARG, TAG, "wrong LED pixel format, expected 4 bytes per pixel");
uint8_t *buf_start = rmt_strip->pixel_buf + index * 4;
// SK6812 component order is GRBW
*buf_start = green & 0xFF;
*++buf_start = red & 0xFF;
*++buf_start = blue & 0xFF;
*++buf_start = white & 0xFF;
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
rmt_transmit_config_t tx_conf = {
.loop_count = 0,
};
ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf,
rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed");
ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed");
return ESP_OK;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// Write zero to turn off all leds
memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return led_strip_rmt_refresh(strip);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
// for backward compatibility, if the user does not set the clk_src, use the default value
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
if (rmt_config->clk_src) {
clk_src = rmt_config->clk_src;
}
size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
// override the default value if the user sets it
if (rmt_config->mem_block_symbols) {
mem_block_symbols = rmt_config->mem_block_symbols;
}
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = clk_src,
.gpio_num = led_config->strip_gpio_num,
.mem_block_symbols = mem_block_symbols,
.resolution_hz = resolution,
.trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE,
.flags.with_dma = rmt_config->flags.with_dma,
.flags.invert_out = led_config->flags.invert_out,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = resolution,
.led_model = led_config->led_model
};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
if (rmt_strip->rmt_chan) {
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder) {
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}

View file

@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt.h"
#include "led_strip.h"
#include "led_strip_interface.h"
static const char *TAG = "led_strip_rmt";
#define WS2812_T0H_NS (300)
#define WS2812_T0L_NS (900)
#define WS2812_T1H_NS (900)
#define WS2812_T1L_NS (300)
#define SK6812_T0H_NS (300)
#define SK6812_T0L_NS (900)
#define SK6812_T1H_NS (600)
#define SK6812_T1L_NS (600)
#define LED_STRIP_RESET_MS (10)
// the memory size of each RMT channel, in words (4 bytes)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
#else
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
#endif
static uint32_t led_t0h_ticks = 0;
static uint32_t led_t1h_ticks = 0;
static uint32_t led_t0l_ticks = 0;
static uint32_t led_t1l_ticks = 0;
typedef struct {
led_strip_t base;
rmt_channel_t rmt_channel;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t buffer[0];
} led_strip_rmt_obj;
static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
size_t wanted_num, size_t *translated_size, size_t *item_num)
{
if (src == NULL || dest == NULL) {
*translated_size = 0;
*item_num = 0;
return;
}
const rmt_item32_t bit0 = {{{ led_t0h_ticks, 1, led_t0l_ticks, 0 }}}; //Logical 0
const rmt_item32_t bit1 = {{{ led_t1h_ticks, 1, led_t1l_ticks, 0 }}}; //Logical 1
size_t size = 0;
size_t num = 0;
uint8_t *psrc = (uint8_t *)src;
rmt_item32_t *pdest = dest;
while (size < src_size && num < wanted_num) {
for (int i = 0; i < 8; i++) {
// MSB first
if (*psrc & (1 << (7 - i))) {
pdest->val = bit1.val;
} else {
pdest->val = bit0.val;
}
num++;
pdest++;
}
size++;
psrc++;
}
*translated_size = size;
*item_num = num;
}
static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of the maximum number of leds");
uint32_t start = index * rmt_strip->bytes_per_pixel;
// In thr order of GRB
rmt_strip->buffer[start + 0] = green & 0xFF;
rmt_strip->buffer[start + 1] = red & 0xFF;
rmt_strip->buffer[start + 2] = blue & 0xFF;
if (rmt_strip->bytes_per_pixel > 3) {
rmt_strip->buffer[start + 3] = 0;
}
return ESP_OK;
}
static esp_err_t led_strip_rmt_refresh(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_write_sample(rmt_strip->rmt_channel, rmt_strip->buffer, rmt_strip->strip_len * rmt_strip->bytes_per_pixel, true), TAG,
"transmit RMT samples failed");
vTaskDelay(pdMS_TO_TICKS(LED_STRIP_RESET_MS));
return ESP_OK;
}
static esp_err_t led_strip_rmt_clear(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
// Write zero to turn off all LEDs
memset(rmt_strip->buffer, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return led_strip_rmt_refresh(strip);
}
static esp_err_t led_strip_rmt_del(led_strip_t *strip)
{
led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_driver_uninstall(rmt_strip->rmt_channel), TAG, "uninstall RMT driver failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *dev_config, led_strip_handle_t *ret_strip)
{
led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(led_config && dev_config && ret_strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, TAG, "invalid led_pixel_format");
ESP_RETURN_ON_FALSE(dev_config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, TAG, "DMA is not supported");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
// allocate memory for led_strip object
rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel);
ESP_RETURN_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, TAG, "request memory for les_strip failed");
// install RMT channel driver
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(led_config->strip_gpio_num, dev_config->rmt_channel);
// set the minimal clock division because the LED strip needs a high clock resolution
config.clk_div = 2;
uint8_t mem_block_num = 2;
// override the default value if the user specify the mem block size
if (dev_config->mem_block_symbols) {
mem_block_num = (dev_config->mem_block_symbols + LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS / 2) / LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
}
config.mem_block_num = mem_block_num;
ESP_GOTO_ON_ERROR(rmt_config(&config), err, TAG, "RMT config failed");
ESP_GOTO_ON_ERROR(rmt_driver_install(config.channel, 0, 0), err, TAG, "RMT install failed");
uint32_t counter_clk_hz = 0;
rmt_get_counter_clock((rmt_channel_t)dev_config->rmt_channel, &counter_clk_hz);
// ns -> ticks
float ratio = (float)counter_clk_hz / 1e9;
if (led_config->led_model == LED_MODEL_WS2812) {
led_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
led_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
led_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
led_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
} else if (led_config->led_model == LED_MODEL_SK6812) {
led_t0h_ticks = (uint32_t)(ratio * SK6812_T0H_NS);
led_t0l_ticks = (uint32_t)(ratio * SK6812_T0L_NS);
led_t1h_ticks = (uint32_t)(ratio * SK6812_T1H_NS);
led_t1l_ticks = (uint32_t)(ratio * SK6812_T1L_NS);
} else {
assert(false);
}
// adapter to translates the LES strip date frame into RMT symbols
rmt_translator_init((rmt_channel_t)dev_config->rmt_channel, ws2812_rmt_adapter);
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->rmt_channel = (rmt_channel_t)dev_config->rmt_channel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->base.set_pixel = led_strip_rmt_set_pixel;
rmt_strip->base.refresh = led_strip_rmt_refresh;
rmt_strip->base.clear = led_strip_rmt_clear;
rmt_strip->base.del = led_strip_rmt_del;
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip) {
free(rmt_strip);
}
return ret;
}

View file

@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "led_strip_rmt_encoder.h"
static const char *TAG = "led_rmt_encoder";
typedef struct {
rmt_encoder_t base;
rmt_encoder_t *bytes_encoder;
rmt_encoder_t *copy_encoder;
int state;
rmt_symbol_word_t reset_code;
} rmt_led_strip_encoder_t;
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
rmt_encode_state_t session_state = 0;
rmt_encode_state_t state = 0;
size_t encoded_symbols = 0;
switch (led_encoder->state) {
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
free(led_encoder);
return ESP_OK;
}
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_led_strip_encoder_t *led_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model");
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip;
led_encoder->base.del = rmt_del_led_strip_encoder;
led_encoder->base.reset = rmt_led_strip_encoder_reset;
rmt_bytes_encoder_config_t bytes_encoder_config;
if (config->led_model == LED_MODEL_SK6812) {
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.6 * config->resolution / 1000000, // T1H=0.6us
.level1 = 0,
.duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us
},
.flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0)
};
} else if (config->led_model == LED_MODEL_WS2812) {
// different led strip might have its own timing requirements, following parameter is for WS2812
bytes_encoder_config = (rmt_bytes_encoder_config_t) {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
};
} else {
assert(false);
}
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
uint32_t reset_ticks = config->resolution / 1000000 * 280 / 2; // reset code duration defaults to 280us to accomodate WS2812B-V5
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#include "led_strip_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of led strip encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
led_model_t led_model; /*!< LED model */
} led_strip_encoder_config_t;
/**
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,209 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_rom_gpio.h"
#include "soc/spi_periph.h"
#include "led_strip.h"
#include "led_strip_interface.h"
#include "hal/spi_hal.h"
#define LED_STRIP_SPI_DEFAULT_RESOLUTION (2.5 * 1000 * 1000) // 2.5MHz resolution
#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4
#define SPI_BYTES_PER_COLOR_BYTE 3
#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8)
static const char *TAG = "led_strip_spi";
typedef struct {
led_strip_t base;
spi_host_device_t spi_host;
spi_device_handle_t spi_device;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t pixel_buf[];
} led_strip_spi_obj;
// please make sure to zero-initialize the buf before calling this function
static void __led_strip_spi_bit(uint8_t data, uint8_t *buf)
{
// Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110
// So a color byte occupies 3 bytes of SPI.
*(buf + 2) |= data & BIT(0) ? BIT(2) | BIT(1) : BIT(2);
*(buf + 2) |= data & BIT(1) ? BIT(5) | BIT(4) : BIT(5);
*(buf + 2) |= data & BIT(2) ? BIT(7) : 0x00;
*(buf + 1) |= BIT(0);
*(buf + 1) |= data & BIT(3) ? BIT(3) | BIT(2) : BIT(3);
*(buf + 1) |= data & BIT(4) ? BIT(6) | BIT(5) : BIT(6);
*(buf + 0) |= data & BIT(5) ? BIT(1) | BIT(0) : BIT(1);
*(buf + 0) |= data & BIT(6) ? BIT(4) | BIT(3) : BIT(4);
*(buf + 0) |= data & BIT(7) ? BIT(7) | BIT(6) : BIT(7);
}
static esp_err_t led_strip_spi_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
// LED_PIXEL_FORMAT_GRB takes 72bits(9bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
memset(spi_strip->pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
__led_strip_spi_bit(green, &spi_strip->pixel_buf[start]);
__led_strip_spi_bit(red, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE]);
__led_strip_spi_bit(blue, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 2]);
if (spi_strip->bytes_per_pixel > 3) {
__led_strip_spi_bit(0, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 3]);
}
return ESP_OK;
}
static esp_err_t led_strip_spi_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(spi_strip->bytes_per_pixel == 4, ESP_ERR_INVALID_ARG, TAG, "wrong LED pixel format, expected 4 bytes per pixel");
// LED_PIXEL_FORMAT_GRBW takes 96bits(12bytes)
uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE;
// SK6812 component order is GRBW
memset(spi_strip->pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
__led_strip_spi_bit(green, &spi_strip->pixel_buf[start]);
__led_strip_spi_bit(red, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE]);
__led_strip_spi_bit(blue, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 2]);
__led_strip_spi_bit(white, &spi_strip->pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * 3]);
return ESP_OK;
}
static esp_err_t led_strip_spi_refresh(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
spi_transaction_t tx_conf;
memset(&tx_conf, 0, sizeof(tx_conf));
tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE;
tx_conf.tx_buffer = spi_strip->pixel_buf;
tx_conf.rx_buffer = NULL;
ESP_RETURN_ON_ERROR(spi_device_transmit(spi_strip->spi_device, &tx_conf), TAG, "transmit pixels by SPI failed");
return ESP_OK;
}
static esp_err_t led_strip_spi_clear(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
//Write zero to turn off all leds
memset(spi_strip->pixel_buf, 0, spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE);
uint8_t *buf = spi_strip->pixel_buf;
for (int index = 0; index < spi_strip->strip_len * spi_strip->bytes_per_pixel; index++) {
__led_strip_spi_bit(0, buf);
buf += SPI_BYTES_PER_COLOR_BYTE;
}
return led_strip_spi_refresh(strip);
}
static esp_err_t led_strip_spi_del(led_strip_t *strip)
{
led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base);
ESP_RETURN_ON_ERROR(spi_bus_remove_device(spi_strip->spi_device), TAG, "delete spi device failed");
ESP_RETURN_ON_ERROR(spi_bus_free(spi_strip->spi_host), TAG, "free spi bus failed");
free(spi_strip);
return ESP_OK;
}
esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip)
{
led_strip_spi_obj *spi_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && spi_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW) {
bytes_per_pixel = 4;
} else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB) {
bytes_per_pixel = 3;
} else {
assert(false);
}
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
if (spi_config->flags.with_dma) {
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
spi_strip = heap_caps_calloc(1, sizeof(led_strip_spi_obj) + led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
ESP_GOTO_ON_FALSE(spi_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for spi strip");
spi_strip->spi_host = spi_config->spi_bus;
// for backward compatibility, if the user does not set the clk_src, use the default value
spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
if (spi_config->clk_src) {
clk_src = spi_config->clk_src;
}
spi_bus_config_t spi_bus_cfg = {
.mosi_io_num = led_config->strip_gpio_num,
//Only use MOSI to generate the signal, set -1 when other pins are not used.
.miso_io_num = -1,
.sclk_io_num = -1,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE,
};
ESP_GOTO_ON_ERROR(spi_bus_initialize(spi_strip->spi_host, &spi_bus_cfg, spi_config->flags.with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED), err, TAG, "create SPI bus failed");
if (led_config->flags.invert_out == true) {
esp_rom_gpio_connect_out_signal(led_config->strip_gpio_num, spi_periph_signal[spi_strip->spi_host].spid_out, true, false);
}
spi_device_interface_config_t spi_dev_cfg = {
.clock_source = clk_src,
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.mode = 0,
//set -1 when CS is not used
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
};
ESP_GOTO_ON_ERROR(spi_bus_add_device(spi_strip->spi_host, &spi_dev_cfg, &spi_strip->spi_device), err, TAG, "Failed to add spi device");
int clock_resolution_khz = 0;
spi_device_get_actual_freq(spi_strip->spi_device, &clock_resolution_khz);
// TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution
// But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION
ESP_GOTO_ON_FALSE(clock_resolution_khz == LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000, ESP_ERR_NOT_SUPPORTED, err,
TAG, "unsupported clock resolution:%dKHz", clock_resolution_khz);
spi_strip->bytes_per_pixel = bytes_per_pixel;
spi_strip->strip_len = led_config->max_leds;
spi_strip->base.set_pixel = led_strip_spi_set_pixel;
spi_strip->base.set_pixel_rgbw = led_strip_spi_set_pixel_rgbw;
spi_strip->base.refresh = led_strip_spi_refresh;
spi_strip->base.clear = led_strip_spi_clear;
spi_strip->base.del = led_strip_spi_del;
*ret_strip = &spi_strip->base;
return ESP_OK;
err:
if (spi_strip) {
if (spi_strip->spi_device) {
spi_bus_remove_device(spi_strip->spi_device);
}
if (spi_strip->spi_host) {
spi_bus_free(spi_strip->spi_host);
}
free(spi_strip);
}
return ret;
}

View file

@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_check.h"
#include "led_strip_types.h"
#include "muxed_led_strip_types.h"
#include "muxed_led_strip.h"
#include "muxed_led_strip_interface.h"
static const char *TAG = "muxed_led_strip";
esp_err_t muxed_led_strip_set_pixel(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel(strip, channel, index, red, green, blue);
}
esp_err_t muxed_led_strip_set_pixel_hsv(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
uint32_t rgb_max = value;
uint32_t rgb_min = rgb_max * (255 - saturation) / 255.0f;
uint32_t i = hue / 60;
uint32_t diff = hue % 60;
// RGB adjustment amount by hue
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
switch (i) {
case 0:
red = rgb_max;
green = rgb_min + rgb_adj;
blue = rgb_min;
break;
case 1:
red = rgb_max - rgb_adj;
green = rgb_max;
blue = rgb_min;
break;
case 2:
red = rgb_min;
green = rgb_max;
blue = rgb_min + rgb_adj;
break;
case 3:
red = rgb_min;
green = rgb_max - rgb_adj;
blue = rgb_max;
break;
case 4:
red = rgb_min + rgb_adj;
green = rgb_min;
blue = rgb_max;
break;
default:
red = rgb_max;
green = rgb_min;
blue = rgb_max - rgb_adj;
break;
}
return strip->set_pixel(strip, channel, index, red, green, blue);
}
esp_err_t muxed_led_strip_set_pixel_rgbw(muxed_led_strip_handle_t strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->set_pixel_rgbw(strip, channel, index, red, green, blue, white);
}
esp_err_t muxed_led_strip_refresh(muxed_led_strip_handle_t strip, uint32_t channel)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->refresh(strip, channel);
}
esp_err_t muxed_led_strip_clear(muxed_led_strip_handle_t strip, uint32_t channel)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->clear(strip, channel);
}
esp_err_t muxed_led_strip_del(muxed_led_strip_handle_t strip)
{
ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return strip->del(strip);
}

View file

@ -0,0 +1,186 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/rmt_tx.h"
#include "muxed_led_strip.h"
#include "muxed_led_strip_interface.h"
#include "led_strip_rmt_encoder.h"
#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution
#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4
// the memory size of each RMT channel, in words (4 bytes)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64
#else
#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48
#endif
static const char *TAG = "led_strip_rmt";
typedef struct
{
muxed_led_strip_t base;
rmt_channel_handle_t rmt_chan;
rmt_encoder_handle_t strip_encoder;
uint32_t strip_len;
uint8_t bytes_per_pixel;
uint8_t n_channels;
uint8_t pixel_buf[];
} muxed_led_strip_rmt_obj;
static esp_err_t muxed_led_strip_rmt_set_pixel(muxed_led_strip_t *strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue)
{
muxed_led_strip_rmt_obj *rmt_strip = __containerof(strip, muxed_led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(channel < rmt_strip->n_channels, ESP_ERR_INVALID_ARG, TAG, "channel greater than total channels");
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
uint32_t start = (channel * rmt_strip->strip_len * rmt_strip->bytes_per_pixel) + (index * rmt_strip->bytes_per_pixel);
// In thr order of GRB, as LED strip like WS2812 sends out pixels in this order
rmt_strip->pixel_buf[start + 0] = green & 0xFF;
rmt_strip->pixel_buf[start + 1] = red & 0xFF;
rmt_strip->pixel_buf[start + 2] = blue & 0xFF;
if (rmt_strip->bytes_per_pixel > 3)
{
rmt_strip->pixel_buf[start + 3] = 0;
}
return ESP_OK;
}
static esp_err_t muxed_led_strip_rmt_set_pixel_rgbw(muxed_led_strip_t *strip, uint32_t channel, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white)
{
muxed_led_strip_rmt_obj *rmt_strip = __containerof(strip, muxed_led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(channel < rmt_strip->n_channels, ESP_ERR_INVALID_ARG, TAG, "channel greater than total channels");
ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs");
ESP_RETURN_ON_FALSE(rmt_strip->bytes_per_pixel == 4, ESP_ERR_INVALID_ARG, TAG, "wrong LED pixel format, expected 4 bytes per pixel");
uint8_t *buf_start = rmt_strip->pixel_buf + (channel * rmt_strip->strip_len * rmt_strip->bytes_per_pixel) + (index * 4);
// SK6812 component order is GRBW
*buf_start = green & 0xFF;
*++buf_start = red & 0xFF;
*++buf_start = blue & 0xFF;
*++buf_start = white & 0xFF;
return ESP_OK;
}
static esp_err_t muxed_led_strip_rmt_refresh(muxed_led_strip_t *strip, uint32_t channel)
{
muxed_led_strip_rmt_obj *rmt_strip = __containerof(strip, muxed_led_strip_rmt_obj, base);
rmt_transmit_config_t tx_conf = {
.loop_count = 0,
};
ESP_RETURN_ON_FALSE(channel < rmt_strip->n_channels, ESP_ERR_INVALID_ARG, TAG, "channel greater than total channels");
ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf + (channel * rmt_strip->strip_len * rmt_strip->bytes_per_pixel),
rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf),
TAG, "transmit pixels by RMT failed");
ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed");
return ESP_OK;
}
static esp_err_t muxed_led_strip_rmt_clear(muxed_led_strip_t *strip, uint32_t channel)
{
muxed_led_strip_rmt_obj *rmt_strip = __containerof(strip, muxed_led_strip_rmt_obj, base);
ESP_RETURN_ON_FALSE(channel < rmt_strip->n_channels, ESP_ERR_INVALID_ARG, TAG, "channel greater than total channels");
// Write zero to turn off all leds
memset(rmt_strip->pixel_buf + (channel * rmt_strip->strip_len * rmt_strip->bytes_per_pixel), 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel);
return muxed_led_strip_rmt_refresh(strip, channel);
}
static esp_err_t muxed_led_strip_rmt_del(muxed_led_strip_t *strip)
{
muxed_led_strip_rmt_obj *rmt_strip = __containerof(strip, muxed_led_strip_rmt_obj, base);
ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed");
ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed");
free(rmt_strip);
return ESP_OK;
}
esp_err_t muxed_led_strip_new_rmt_device(const muxed_led_strip_config_t *led_config,
const led_strip_rmt_config_t *rmt_config,
const rmt_tx_event_callbacks_t *cbs,
muxed_led_strip_handle_t *ret_strip)
{
muxed_led_strip_rmt_obj *rmt_strip = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(led_config->led_pixel_format < LED_PIXEL_FORMAT_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led_pixel_format");
uint8_t bytes_per_pixel = 3;
if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRBW)
{
bytes_per_pixel = 4;
}
else if (led_config->led_pixel_format == LED_PIXEL_FORMAT_GRB)
{
bytes_per_pixel = 3;
}
else
{
assert(false);
}
uint8_t number_of_multiplexed_channels = led_config->channels;
rmt_strip = calloc(1, sizeof(muxed_led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel * number_of_multiplexed_channels);
ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip");
uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION;
// for backward compatibility, if the user does not set the clk_src, use the default value
rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT;
if (rmt_config->clk_src)
{
clk_src = rmt_config->clk_src;
}
size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS;
// override the default value if the user sets it
if (rmt_config->mem_block_symbols)
{
mem_block_symbols = rmt_config->mem_block_symbols;
}
rmt_tx_channel_config_t rmt_chan_config = {
.clk_src = clk_src,
.gpio_num = led_config->strip_gpio_num,
.mem_block_symbols = mem_block_symbols,
.resolution_hz = resolution,
.trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE,
.flags.with_dma = rmt_config->flags.with_dma,
.flags.invert_out = led_config->flags.invert_out,
};
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed");
led_strip_encoder_config_t strip_encoder_conf = {
.resolution = resolution,
.led_model = led_config->led_model};
ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed");
rmt_strip->bytes_per_pixel = bytes_per_pixel;
rmt_strip->strip_len = led_config->max_leds;
rmt_strip->n_channels = number_of_multiplexed_channels;
rmt_strip->base.set_pixel = muxed_led_strip_rmt_set_pixel;
rmt_strip->base.set_pixel_rgbw = muxed_led_strip_rmt_set_pixel_rgbw;
rmt_strip->base.refresh = muxed_led_strip_rmt_refresh;
rmt_strip->base.clear = muxed_led_strip_rmt_clear;
rmt_strip->base.del = muxed_led_strip_rmt_del;
ESP_GOTO_ON_ERROR(rmt_tx_register_event_callbacks(rmt_strip->rmt_chan, cbs, (void *)0), err, TAG, "register RMT TX callbacks failed");
*ret_strip = &rmt_strip->base;
return ESP_OK;
err:
if (rmt_strip)
{
if (rmt_strip->rmt_chan)
{
rmt_del_channel(rmt_strip->rmt_chan);
}
if (rmt_strip->strip_encoder)
{
rmt_del_encoder(rmt_strip->strip_encoder);
}
free(rmt_strip);
}
return ret;
}

View file

@ -0,0 +1,17 @@
idf_component_register(
SRCS
"Key_Value.c"
"Settings.c"
"SPIFFS.c"
"USB.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"spiffs"
"driver"
"usb"
"usb_host_msc"
"esp_timer"
)

228
components/NVM/Key_Value.c Normal file
View file

@ -0,0 +1,228 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <SystemK.h>
#define MAX_LINE_LENGTH 140
#define MAX_KEY_LENGTH 64
#define MAX_VALUE_LENGTH 64
static const char *TAG = "NVM KV";
static const char *TEMP_FILE = "/usb/esp/temp.txt";
// Trims the whitespace from both ends of a string.
static void KV_Trim(char *str)
{
char *start = str;
char *end = str + strlen(str) - 1;
while (*start && (*start == ' ' || *start == '\t'))
start++;
while (end > start && (*end == ' ' || *end == '\t' || *end == '\n'))
end--;
*(end + 1) = '\0';
memmove(str, start, end - start + 2);
}
// Splits a line around the '=' into a key and value pair.
static int KV_Parse_Line(char *line, char *key, char *value)
{
char *delimiter = strchr(line, '=');
if (delimiter == NULL)
return 0;
*delimiter = '\0';
strncpy(key, line, MAX_KEY_LENGTH - 1);
strncpy(value, delimiter + 1, MAX_VALUE_LENGTH - 1);
key[MAX_KEY_LENGTH - 1] = '\0';
value[MAX_VALUE_LENGTH - 1] = '\0';
KV_Trim(key);
KV_Trim(value);
return 1;
}
SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
return SYSTEMK_RESULT_FILE_NOT_FOUND;
}
char line[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
while (fgets(line, sizeof(line), file))
{
if (KV_Parse_Line(line, key, value) && strcmp(key, search_key) == 0)
{
fclose(file);
return SYSTEMK_RESULT_SUCCESS;
}
}
fclose(file);
KLOG_ERROR(TAG, "Couldn't find key %s in file %s!", search_key, filename);
return SYSTEMK_RESULT_KEY_NOT_FOUND;
}
SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value)
{
FILE *file = fopen(filename, "r");
if (file == NULL)
{
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
return SYSTEMK_RESULT_FILE_NOT_FOUND;
}
FILE *temp = fopen(TEMP_FILE, "w");
if (temp == NULL)
{
fclose(file);
KLOG_ERROR(TAG, "Couldn't open file %s for writing!", TEMP_FILE);
return SYSTEMK_RESULT_WRITE_FAILED;
}
char line[MAX_LINE_LENGTH];
char line_copy[MAX_LINE_LENGTH];
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
int found = 0;
while (fgets(line, sizeof(line), file))
{
strncpy(line_copy, line, MAX_LINE_LENGTH);
line_copy[MAX_LINE_LENGTH - 1] = '\0'; // Ensure null-termination
if (KV_Parse_Line(line, key, value) && strcmp(key, set_key) == 0)
{
fprintf(temp, "%s = %s\n", set_key, set_value);
found = 1;
}
else
{
fputs(line_copy, temp);
}
}
if (!found)
{
fprintf(temp, "%s = %s\n", set_key, set_value);
}
fclose(file);
fclose(temp);
remove(filename);
rename(TEMP_FILE, filename);
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value)
{
char value_str[MAX_VALUE_LENGTH];
SystemKResult_T result = KV_Get_Value_string(filename, search_key, value_str);
if (result != SYSTEMK_RESULT_SUCCESS)
{
return result;
}
char *endptr;
// Reset errno to check for conversion errors.
errno = 0;
*value = strtoul(value_str, &endptr, 10);
// Check for conversion errors
if ((errno == ERANGE && *value == ULONG_MAX) || (errno != 0))
{
KLOG_ERROR(TAG, "Error converting %s for key %s to uint32_t!", value_str, search_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
if (endptr == value_str)
{
KLOG_ERROR(TAG, "No digits were found in %s (for key %s)!", value_str, search_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
if (*value > UINT32_MAX)
{
KLOG_ERROR(TAG, "Value %s (for key %s) exceeds uint32_t range!", value_str, search_key);
return SYSTEMK_RESULT_OVERFLOW;
}
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value)
{
char value_str[MAX_VALUE_LENGTH];
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%lu", *set_value);
if (written < 0 || written >= MAX_VALUE_LENGTH)
{
KLOG_ERROR(TAG, "Error converting uint32_t to string for key %s!", set_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
return KV_Set_Value_string(filename, set_key, value_str);
}
SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value)
{
uint32_t value32 = 0;
SystemKResult_T result = KV_Get_Value_uint32(filename, search_key, &value32);
if (result != SYSTEMK_RESULT_SUCCESS)
{
return result;
}
// Check for conversion errors
if (value32 > UINT8_MAX)
{
KLOG_ERROR(TAG, "Value %lu (for key %s) exceeds uint32_t range!", value32, search_key);
return SYSTEMK_RESULT_OVERFLOW;
}
*value = (uint8_t)value32;
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value)
{
char value_str[MAX_VALUE_LENGTH];
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%u", *set_value);
if (written < 0 || written >= MAX_VALUE_LENGTH)
{
KLOG_ERROR(TAG, "Error converting uint8_t to string for key %s!", set_key);
return SYSTEMK_RESULT_WRONG_DATATYPE;
}
return KV_Set_Value_string(filename, set_key, value_str);
}

View file

@ -0,0 +1,29 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value);
SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value);
SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value);
SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value);
SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value);
SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value);

26
components/NVM/NVM.h Normal file
View file

@ -0,0 +1,26 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "Key_Value.h"
#include "SPIFFS.h"
#include "USB.h"

84
components/NVM/SPIFFS.c Normal file
View file

@ -0,0 +1,84 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#include <SystemK.h>
#include <string.h>
#include "esp_spiffs.h"
static const char *TAG = "SPIFFS";
const char *DEFAULT_CONFIG_FILE = "/spiffs/default_config.txt";
void Initialize_SPIFFS(SemaphoreHandle_t init_complete)
{
KLOG_INFO(TAG, "Initializing SPI flash file system (SPIFFS)...");
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs", // File path prefix associated with the filesystem.
.partition_label = NULL, // If set to NULL, first partition with subtype=spiffs will be used.
.max_files = 5, // Maximum files that could be open at the same time.
.format_if_mount_failed = false}; // If true, it will format the file system if it fails to mount.
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
KLOG_ERROR(TAG, "Failed to mount or format filesystem!");
}
else if (ret == ESP_ERR_NOT_FOUND)
{
KLOG_ERROR(TAG, "Failed to find SPIFFS partition!");
}
else
{
KLOG_ERROR(TAG, "Failed to initialize SPIFFS (%s)!", esp_err_to_name(ret));
}
return;
}
size_t total = 0;
size_t used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret != ESP_OK)
{
KLOG_ERROR(TAG, "Failed to get SPIFFS partition information (%s)!", esp_err_to_name(ret));
}
else
{
KLOG_INFO(TAG, "SPIFFS partition: %d bytes used out of %d total.", used, total);
}
FILE *f = fopen("/spiffs/boot_message.txt", "r");
if (f == NULL)
{
KLOG_ERROR(TAG, "Failed to open boot_message.txt!");
return;
}
char buf[64];
memset(buf, 0, sizeof(buf));
fread(buf, 1, sizeof(buf), f);
fclose(f);
KLOG_INFO(TAG, ">>> %s <<<", buf);
xSemaphoreGive(init_complete);
}

26
components/NVM/SPIFFS.h Normal file
View file

@ -0,0 +1,26 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
void Initialize_SPIFFS(SemaphoreHandle_t init_complete);
extern const char *DEFAULT_CONFIG_FILE;

209
components/NVM/Settings.c Normal file
View file

@ -0,0 +1,209 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#include <SystemK.h>
#include <NVM.h>
static const uint8_t UNINTIALIZED_UINT8 = UINT8_MAX;
static uint8_t Cached_Team_ID = UNINTIALIZED_UINT8;
static uint8_t Cached_Player_ID = UNINTIALIZED_UINT8;
static uint8_t Cached_Weapon_ID = UNINTIALIZED_UINT8;
SystemKResult_T SETTINGS_get_uint8_t(SystemKSettingID_T id, uint8_t *value)
{
SystemKResult_T result = SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
char *key = "";
uint8_t *cached_value = NULL;
switch (id)
{
case SYSTEMK_SETTING_IS_RIGHT_HANDED:
key = "Is_Right_Handed";
break;
case SYSTEMK_SETTING_AUDIO_VOLUME:
key = "Audio_Volume";
break;
case SYSTEMK_SETTING_TEAMID:
if (Cached_Team_ID != UNINTIALIZED_UINT8)
{
*value = Cached_Team_ID;
result = SYSTEMK_RESULT_SUCCESS;
}
else
{
key = "Team_ID";
cached_value = &Cached_Team_ID;
}
break;
case SYSTEMK_SETTING_PLAYERID:
if (Cached_Player_ID != UNINTIALIZED_UINT8)
{
*value = Cached_Player_ID;
result = SYSTEMK_RESULT_SUCCESS;
}
else
{
key = "Player_ID";
cached_value = &Cached_Player_ID;
}
break;
case SYSTEMK_SETTING_WEAPONID:
if (Cached_Weapon_ID != UNINTIALIZED_UINT8)
{
*value = Cached_Weapon_ID;
result = SYSTEMK_RESULT_SUCCESS;
}
else
{
key = "Weapon_ID";
cached_value = &Cached_Weapon_ID;
}
break;
default:
result = SYSTEMK_RESULT_WRONG_DATATYPE;
break;
}
if (result != SYSTEMK_RESULT_SUCCESS)
{
result = KV_Get_Value_uint8(CONFIG_FILE, key, value);
if (result != SYSTEMK_RESULT_SUCCESS)
{
result = KV_Get_Value_uint8(DEFAULT_CONFIG_FILE, key, value);
if (result == SYSTEMK_RESULT_SUCCESS)
{
(void)KV_Set_Value_uint8(CONFIG_FILE, key, value);
}
}
// Save the cached value, if necessary.
if ((cached_value != NULL) && (result == SYSTEMK_RESULT_SUCCESS))
{
*cached_value = *value;
}
}
return result;
}
SystemKResult_T SETTINGS_set_uint8_t(SystemKSettingID_T id, uint8_t value)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
switch (id)
{
case SYSTEMK_SETTING_IS_RIGHT_HANDED:
result = KV_Set_Value_uint8(CONFIG_FILE, "Is_Right_Handed", &value);
break;
case SYSTEMK_SETTING_AUDIO_VOLUME:
result = KV_Set_Value_uint8(CONFIG_FILE, "Audio_Volume", &value);
break;
case SYSTEMK_SETTING_TEAMID:
Cached_Team_ID = value;
result = KV_Set_Value_uint8(CONFIG_FILE, "Team_ID", &value);
break;
case SYSTEMK_SETTING_PLAYERID:
Cached_Player_ID = value;
result = KV_Set_Value_uint8(CONFIG_FILE, "Player_ID", &value);
break;
case SYSTEMK_SETTING_WEAPONID:
Cached_Weapon_ID = value;
result = KV_Set_Value_uint8(CONFIG_FILE, "Weapon_ID", &value);
break;
default:
result = SYSTEMK_RESULT_WRONG_DATATYPE;
break;
}
return result;
}
SystemKResult_T SETTINGS_get_uint32_t(SystemKSettingID_T id, uint32_t *value)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
char *key = "";
switch (id)
{
case SYSTEMK_SETTING_T_START_GAME_in_ms:
key = "T_Start_Game_in_ms";
break;
default:
result = SYSTEMK_RESULT_WRONG_DATATYPE;
break;
}
if (result == SYSTEMK_RESULT_SUCCESS)
{
result = KV_Get_Value_uint32(CONFIG_FILE, key, value);
if (result != SYSTEMK_RESULT_SUCCESS)
{
result = KV_Get_Value_uint32(DEFAULT_CONFIG_FILE, key, value);
if (result == SYSTEMK_RESULT_SUCCESS)
{
(void)KV_Set_Value_uint32(CONFIG_FILE, key, value);
}
}
}
return result;
}
SystemKResult_T SETTINGS_set_uint32_t(SystemKSettingID_T id, uint32_t value)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
switch (id)
{
case SYSTEMK_SETTING_T_START_GAME_in_ms:
result = KV_Set_Value_uint32(CONFIG_FILE, "T_Start_Game_in_ms", &value);
break;
default:
result = SYSTEMK_RESULT_WRONG_DATATYPE;
break;
}
return result;
}
// Settings are saved on change in this implementation.
SystemKResult_T SETTINGS_Save(void)
{
return SYSTEMK_RESULT_SUCCESS;
}

449
components/NVM/USB.c Normal file
View file

@ -0,0 +1,449 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
// From https://github.com/espressif/esp-idf/blob/master/examples/peripherals/usb/host/msc/main/msc_example_main.c
#include <SystemK.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/stat.h>
#include <dirent.h>
#include <inttypes.h>
#include <errno.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "usb/usb_host.h"
#include "usb/msc_host.h"
#include "usb/msc_host_vfs.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "Key_Value.h"
#include "USB.h"
#define STACK_SIZE 4096
static StaticTask_t xTaskBuffer;
static StackType_t xStack[STACK_SIZE];
#define USB_HOST_TASK_PRIORITY 2
#define MSC_HOST_TASK_PRIORITY 3
#define MNT_PATH "/usb" // Path in the Virtual File System, where the USB flash drive is going to be mounted
#define BUFFER_SIZE 4096 // The read/write performance can be improved with larger buffer for the cost of RAM; 4kB is enough for most use cases.
static const char *TAG = "USB";
static const char *OTA_FILE = "/usb/esp/OTA_URL.txt";
const char *CONFIG_FILE = "/usb/esp/config.txt";
typedef enum
{
USB_STATE_UNINITIALIZED,
USB_STATE_IDLE,
USB_STATE_DEVICE_CONNECTED,
USB_STATE_VFS_REGISTERED,
USB_STATE_PROCESSING_DISCONNECTION
} USB_State_T;
static QueueHandle_t usb_queue;
typedef struct
{
enum
{
USB_HOST_INITIALIZED,
USB_DEVICE_CONNECTED, // USB device connect event
USB_DEVICE_DISCONNECTED, // USB device disconnect event
} id;
union
{
uint8_t new_dev_address; // Address of new USB device for APP_DEVICE_CONNECTED event
} data;
} usb_message_t;
/**
* @brief MSC driver callback
*
* Signal device connection/disconnection to the main task
*
* @param[in] event MSC event
* @param[in] arg MSC event data
*/
static void MSC_Event_Callback(const msc_host_event_t *event, void *arg)
{
if (event->event == MSC_DEVICE_CONNECTED)
{
usb_message_t message = {
.id = USB_DEVICE_CONNECTED,
.data.new_dev_address = event->device.address,
};
xQueueSend(usb_queue, &message, portMAX_DELAY);
}
else if (event->event == MSC_DEVICE_DISCONNECTED)
{
usb_message_t message = {
.id = USB_DEVICE_DISCONNECTED,
};
xQueueSend(usb_queue, &message, portMAX_DELAY);
}
else
{
KLOG_INFO(TAG, "Event: %d", event->event);
}
}
__attribute__((unused)) static void print_device_info(msc_host_device_info_t *info)
{
const size_t megabyte = 1024 * 1024;
uint64_t capacity = ((uint64_t)info->sector_size * info->sector_count) / megabyte;
printf("Device info:\n");
printf("\t Capacity: %llu MB\n", capacity);
printf("\t Sector size: %" PRIu32 "\n", info->sector_size);
printf("\t Sector count: %" PRIu32 "\n", info->sector_count);
printf("\t PID: 0x%04X \n", info->idProduct);
printf("\t VID: 0x%04X \n", info->idVendor);
wprintf(L"\t iProduct: %S \n", info->iProduct);
wprintf(L"\t iManufacturer: %S \n", info->iManufacturer);
wprintf(L"\t iSerialNumber: %S \n", info->iSerialNumber);
}
__attribute__((unused)) static void file_operations(void)
{
const char *directory = "/usb/esp";
const char *file_path = "/usb/esp/test.txt";
// Create /usb/esp directory
struct stat s = {0};
bool directory_exists = stat(directory, &s) == 0;
if (!directory_exists)
{
if (mkdir(directory, 0775) != 0)
{
KLOG_ERROR(TAG, "mkdir failed with errno: %s", strerror(errno));
}
}
// Create /usb/esp/test.txt file, if it doesn't exist
if (stat(file_path, &s) != 0)
{
KLOG_INFO(TAG, "Creating file");
FILE *f = fopen(file_path, "w");
if (f == NULL)
{
KLOG_ERROR(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "Hello World!\n");
fclose(f);
}
// Read back the file
FILE *f;
KLOG_INFO(TAG, "Reading file");
f = fopen(file_path, "r");
if (f == NULL)
{
KLOG_ERROR(TAG, "Failed to open file for reading");
return;
}
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char *pos = strchr(line, '\n');
if (pos)
{
*pos = '\0';
}
KLOG_INFO(TAG, "Read from file '%s': '%s'", file_path, line);
}
__attribute__((unused)) void speed_test(void)
{
#define TEST_FILE "/usb/esp/dummy"
#define ITERATIONS 256 // 256 * 4kb = 1MB
int64_t test_start, test_end;
FILE *f = fopen(TEST_FILE, "wb+");
if (f == NULL)
{
KLOG_ERROR(TAG, "Failed to open file for writing");
return;
}
// Set larger buffer for this file. It results in larger and more effective USB transfers
setvbuf(f, NULL, _IOFBF, BUFFER_SIZE);
// Allocate application buffer used for read/write
uint8_t *data = malloc(BUFFER_SIZE);
assert(data);
KLOG_INFO(TAG, "Writing to file %s", TEST_FILE);
test_start = esp_timer_get_time();
for (int i = 0; i < ITERATIONS; i++)
{
if (fwrite(data, BUFFER_SIZE, 1, f) == 0)
{
return;
}
}
test_end = esp_timer_get_time();
KLOG_INFO(TAG, "Write speed %1.2f MiB/s", (BUFFER_SIZE * ITERATIONS) / (float)(test_end - test_start));
rewind(f);
KLOG_INFO(TAG, "Reading from file %s", TEST_FILE);
test_start = esp_timer_get_time();
for (int i = 0; i < ITERATIONS; i++)
{
if (0 == fread(data, BUFFER_SIZE, 1, f))
{
return;
}
}
test_end = esp_timer_get_time();
KLOG_INFO(TAG, "Read speed %1.2f MiB/s", (BUFFER_SIZE * ITERATIONS) / (float)(test_end - test_start));
fclose(f);
free(data);
}
static void usb_host_task(void *args)
{
usb_host_config_t host_config = {
.skip_phy_setup = false,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
ESP_ERROR_CHECK(usb_host_install(&host_config));
const msc_host_driver_config_t msc_config = {
.create_backround_task = true,
.task_priority = MSC_HOST_TASK_PRIORITY,
.stack_size = 4096,
.callback = MSC_Event_Callback,
};
ESP_ERROR_CHECK(msc_host_install(&msc_config));
vTaskDelay(10); // Short delay to let MSC client task spin up.
usb_message_t message = {
.id = USB_HOST_INITIALIZED};
xQueueSend(usb_queue, &message, portMAX_DELAY);
while (true)
{
uint32_t event_flags;
// This function handles all of the USB Host Library's processing and should be called repeatedly in a loop.
ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags));
// Release devices once all clients have deregistered.
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
{
if (usb_host_device_free_all() == ESP_OK)
{
KLOG_INFO(TAG, "USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS");
// break;
};
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE)
{
KLOG_INFO(TAG, "USB_HOST_LIB_EVENT_FLAGS_ALL_FREE");
// break;
}
}
}
static void app_usb_task(void *args)
{
SemaphoreHandle_t init_complete = args;
static USB_State_T Current_State = USB_STATE_UNINITIALIZED;
static msc_host_device_handle_t msc_device = NULL;
static msc_host_vfs_handle_t vfs_handle = NULL;
static uint8_t device_address = 1;
usb_message_t msg;
while (true)
{
switch (Current_State)
{
case USB_STATE_UNINITIALIZED:
{
if (xQueueReceive(usb_queue, &msg, portMAX_DELAY) == pdTRUE)
{
if (msg.id == USB_HOST_INITIALIZED)
{
KLOG_INFO(TAG, "Host initialized.");
Current_State = USB_STATE_IDLE;
}
}
}
break;
case USB_STATE_IDLE:
{
KLOG_TRACE(TAG, "USB_STATE_IDLE");
if (xQueueReceive(usb_queue, &msg, pdMS_TO_TICKS(1000)) == pdTRUE)
{
if (msg.id == USB_DEVICE_CONNECTED)
{
device_address = msg.data.new_dev_address;
Current_State = USB_STATE_DEVICE_CONNECTED;
KLOG_INFO(TAG, "Device connected.");
}
}
else
{
KLOG_ERROR(TAG, "No flash drive detected--rebooting.");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
}
}
break;
case USB_STATE_DEVICE_CONNECTED:
{
KLOG_TRACE(TAG, "USB_STATE_DEVICE_CONNECTED");
// USB device connected. Open it and attempt to map it to Virtual File System.
if (msc_host_install_device(device_address, &msc_device) != ESP_OK)
{
ESP_LOGW(TAG, "Problem connecting to flash drive.");
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
}
else
{
const esp_vfs_fat_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 3,
.allocation_unit_size = 8192,
};
if (msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle) != ESP_OK)
{
ESP_LOGW(TAG, "Problem mounting filesystem.");
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
}
else
{
Current_State = USB_STATE_VFS_REGISTERED;
xSemaphoreGive(init_complete);
}
}
}
break;
case USB_STATE_VFS_REGISTERED:
{
KLOG_TRACE(TAG, "USB_STATE_VFS_REGISTERED");
if (xQueueReceive(usb_queue, &msg, pdMS_TO_TICKS(1000)) == pdTRUE)
{
if (msg.id == USB_DEVICE_DISCONNECTED)
{
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
}
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
break;
case USB_STATE_PROCESSING_DISCONNECTION:
{
KLOG_TRACE(TAG, "USB_STATE_PROCESSING_DISCONNECTION");
if (vfs_handle)
{
ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
vfs_handle = NULL;
}
if (msc_device)
{
ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
msc_device = NULL;
device_address = 0;
}
Current_State = USB_STATE_IDLE;
}
break;
}
}
}
void Initialize_USB(SemaphoreHandle_t init_complete)
{
KLOG_INFO(TAG, "Initializing USB file system...");
// Create FreeRTOS primitives
usb_queue = xQueueCreate(5, sizeof(usb_message_t));
assert(usb_queue);
BaseType_t usb_host_task_created = xTaskCreatePinnedToCore(usb_host_task, "USB Host", 4096, NULL, USB_HOST_TASK_PRIORITY, NULL, 1);
assert(usb_host_task_created);
TaskHandle_t xHandle = xTaskCreateStaticPinnedToCore(
app_usb_task, // Function that implements the task.
"USB NVM", // Text name for the task.
STACK_SIZE, // Stack size in bytes, not words.
(void *)init_complete, // Parameter passed into the task.
tskIDLE_PRIORITY + 2, // Priority at which the task is created.
xStack, // Array to use as the task's stack.
&xTaskBuffer, // Variable to hold the task's data structure.
APP_CPU_NUM); // Specify the task's core affinity
assert(xHandle);
}
bool OTA_File_Exists(void)
{
struct stat s = {0};
return (stat(OTA_FILE, &s) == 0);
}
char *Get_OTA_Image_URL(void)
{
static char url[64] = "";
FILE *f;
f = fopen(OTA_FILE, "r");
if (f == NULL)
{
KLOG_ERROR(TAG, "The OTA file is missing!");
return url;
}
fgets(url, sizeof(url), f);
fclose(f);
// Strip the newline
char *pos = strchr(url, '\n');
if (pos)
{
*pos = '\0';
}
return url;
}
void Erase_OTA_File(void)
{
if (remove(OTA_FILE) != 0)
{
KLOG_ERROR(TAG, "Error erasing the OTA file: %s", strerror(errno));
}
}

31
components/NVM/USB.h Normal file
View file

@ -0,0 +1,31 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
void Initialize_USB(SemaphoreHandle_t init_complete);
extern const char *CONFIG_FILE;
// OTA Reprogramming Helpers
bool OTA_File_Exists(void);
char * Get_OTA_Image_URL(void);
void Erase_OTA_File(void);

View file

@ -0,0 +1,14 @@
idf_component_register(
SRCS
"Reprogramming.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"driver"
"usb"
"usb_host_msc"
"esp_https_ota"
"app_update"
"NVM"
)

View file

@ -0,0 +1,311 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
#include <SystemK.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <NVM.h>
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_spiffs.h"
#include "esp_partition.h"
static const char *TAG = "Reprogramming";
static const char *FIRMWARE_DIR = "/usb/esp/firmware";
static const char *APP_IMAGE_PREFIX = "APP";
static const char *SPIFFS_IMAGE_PREFIX = "SPIFFS";
#define REPROGRAMMING_TASK_PRIORITY 1
static TaskHandle_t xReprogrammingTask = NULL;
static StaticTask_t xReprogrammingTaskBuffer;
static StackType_t xReprogrammingTaskStack[4096];
#define BUFFER_SIZE 1024
typedef struct
{
unsigned long total_size;
unsigned long remaining_size;
void *data;
} data_manager_t;
typedef enum {
IMAGE_TYPE_APP,
IMAGE_TYPE_SPIFFS
} image_type_t;
static char *find_image_filename(const char *prefix)
{
DIR *dir = opendir(FIRMWARE_DIR);
if (dir == NULL)
{
KLOG_ERROR(TAG, "Failed to open directory '%s': %s!", FIRMWARE_DIR, strerror(errno));
return NULL;
}
struct dirent *entry;
char *filename = NULL;
while ((entry = readdir(dir)) != NULL)
{
if (entry->d_type == DT_REG && strncmp(entry->d_name, prefix, strlen(prefix)) == 0)
{
filename = strdup(entry->d_name);
if (filename == NULL)
{
KLOG_ERROR(TAG, "Memory allocation failed!");
}
else
{
KLOG_DEBUG(TAG, "Found image: %s", filename);
}
break;
}
}
closedir(dir);
if (filename == NULL)
{
KLOG_WARN(TAG, "Couldn't find an image with prefix '%s' in directory '%s'!", prefix, FIRMWARE_DIR);
return NULL;
}
size_t needed = snprintf(NULL, 0, "%s/%s", FIRMWARE_DIR, filename) + 1;
char *full_path = malloc(needed);
if (full_path != NULL)
{
snprintf(full_path, needed, "%s/%s", FIRMWARE_DIR, filename);
}
free(filename);
return full_path;
}
static SystemKResult_T program_image(const char *filename, image_type_t image_type)
{
esp_err_t err;
FILE *file = fopen(filename, "rb");
if (file == NULL)
{
KLOG_ERROR(TAG, "Failed to open file %s", filename);
return SYSTEMK_RESULT_FILE_NOT_FOUND;
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
KLOG_INFO(TAG, "File size: %ld bytes", file_size);
data_manager_t data_manager = {
.total_size = file_size,
.remaining_size = file_size,
.data = malloc(BUFFER_SIZE)
};
if (data_manager.data == NULL)
{
KLOG_ERROR(TAG, "Failed to allocate memory");
fclose(file);
return SYSTEMK_RESULT_NOT_ENOUGH_MEMORY;
}
if (image_type == IMAGE_TYPE_APP)
{
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
KLOG_INFO(TAG, "Writing to partition subtype %d at offset 0x%" PRIx32, update_partition->subtype, update_partition->address);
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
free(data_manager.data);
fclose(file);
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
}
while (data_manager.remaining_size > 0)
{
size_t size = (data_manager.remaining_size <= BUFFER_SIZE) ? data_manager.remaining_size : BUFFER_SIZE;
if (fread(data_manager.data, 1, size, file) != size)
{
KLOG_ERROR(TAG, "fread failed");
err = ESP_FAIL;
break;
}
err = esp_ota_write(update_handle, data_manager.data, size);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "esp_ota_write failed (%s)", esp_err_to_name(err));
break;
}
data_manager.remaining_size -= size;
}
if (err == ESP_OK)
{
err = esp_ota_end(update_handle);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "esp_ota_end failed (%s)", esp_err_to_name(err));
}
else
{
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "esp_ota_set_boot_partition failed (%s)", esp_err_to_name(err));
}
}
}
}
else if (image_type == IMAGE_TYPE_SPIFFS)
{
const esp_partition_t *spiffs_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
if (spiffs_partition == NULL)
{
KLOG_ERROR(TAG, "SPIFFS partition not found");
free(data_manager.data);
fclose(file);
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
}
err = esp_partition_erase_range(spiffs_partition, 0, spiffs_partition->size);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "Failed to erase SPIFFS partition (%s)", esp_err_to_name(err));
free(data_manager.data);
fclose(file);
return SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
}
while (data_manager.remaining_size > 0)
{
size_t size = (data_manager.remaining_size <= BUFFER_SIZE) ? data_manager.remaining_size : BUFFER_SIZE;
if (fread(data_manager.data, 1, size, file) != size)
{
KLOG_ERROR(TAG, "fread failed");
err = ESP_FAIL;
break;
}
err = esp_partition_write(spiffs_partition, spiffs_partition->size - data_manager.remaining_size, data_manager.data, size);
if (err != ESP_OK)
{
KLOG_ERROR(TAG, "esp_partition_write failed (%s)", esp_err_to_name(err));
break;
}
data_manager.remaining_size -= size;
}
}
free(data_manager.data);
fclose(file);
return (err == ESP_OK) ? SYSTEMK_RESULT_SUCCESS : SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
}
SystemKResult_T Maybe_Reprogram_From_USB(void)
{
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
char *app_image_filename = find_image_filename(APP_IMAGE_PREFIX);
char *spiffs_image_filename = find_image_filename(SPIFFS_IMAGE_PREFIX);
if (app_image_filename)
{
KLOG_INFO(TAG, "Application image found: %s", app_image_filename);
result = program_image(app_image_filename, IMAGE_TYPE_APP);
free(app_image_filename);
if (result != SYSTEMK_RESULT_SUCCESS)
{
return result;
}
}
if (spiffs_image_filename)
{
KLOG_INFO(TAG, "SPIFFS image found: %s", spiffs_image_filename);
result = program_image(spiffs_image_filename, IMAGE_TYPE_SPIFFS);
free(spiffs_image_filename);
}
if ((app_image_filename == NULL) && (spiffs_image_filename == NULL))
{
KLOG_ERROR(TAG, "Reprogramming failed--there's nothing to reprogram!");
result = SYSTEMK_RESULT_FILE_NOT_FOUND;
}
return result;
}
static void Reprogramming_Task(void *pvParameters)
{
SystemKResult_T result = Maybe_Reprogram_From_USB();
static All_On_Data_T data;
if (result == SYSTEMK_RESULT_SUCCESS)
{
KLOG_INFO(TAG, "Reprogramming succeeded--rebooting...");
data.color = ApplyMask(COLOR_GREEN, 0x70);
data.style = DISPLAY_STYLE_SOLID;
}
else
{
KLOG_ERROR(TAG, "Reprogramming failed--rebooting...");
data.color = ApplyMask(COLOR_RED, 0x70);
data.style = DISPLAY_STYLE_SOLID;
}
NeoPixelsAction_T neopixels_action = {.ID = NEOPIXELS_ALL_ON, .Prominence = NEOPIXELS_FOREGROUND, .Data = (void *)&data};
xQueueSend(xQueueNeoPixels, &neopixels_action, 0);
vTaskDelay(5000 / portTICK_PERIOD_MS);
esp_restart();
vTaskDelete(NULL);
}
void Request_Reprogramming_From_USB(void)
{
if (xReprogrammingTask == NULL)
{
KLOG_INFO(TAG, "Attempting USB reprogramming...");
xReprogrammingTask = xTaskCreateStaticPinnedToCore(
Reprogramming_Task,
"Reprogramming",
sizeof(xReprogrammingTaskStack) / sizeof(StackType_t),
NULL,
REPROGRAMMING_TASK_PRIORITY,
xReprogrammingTaskStack,
&xReprogrammingTaskBuffer,
APP_CPU_NUM);
}
else
{
KLOG_WARN(TAG, "Reprogramming already in progress.");
}
}

View file

@ -0,0 +1,22 @@
/*
* This program source code file is part of the KTag project.
*
* 🛡 <https://ktag.clubk.club> 🃞
*
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero 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 Affero General Public License for more
* details.
*
* There should be a copy of the GNU Affero General Public License in the LICENSE
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
*/
void Request_Reprogramming_From_USB(void);

View file

@ -0,0 +1,10 @@
idf_component_register(
SRCS
"Switches.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"driver"
"button"
)

View file

@ -0,0 +1,88 @@
#include <SystemK.h>
#include <driver/gpio.h>
#include <iot_button.h>
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
static const char *TAG = "Switches";
// GPIO assignments
#define TRIGGER_GPIO GPIO_NUM_1
#define ACCESSORY_GPIO GPIO_NUM_4
static button_handle_t Trigger_Button;
static button_config_t Trigger_Button_Config = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = TRIGGER_GPIO,
.active_level = 0,
},
};
static button_handle_t Accessory_Button;
static button_config_t Accessory_Button_Config = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = ACCESSORY_GPIO,
.active_level = 0,
},
};
static TickType_t TicksAtTriggerPress = 0;
static TickType_t TicksAtAccessoryPress = 0;
static TickType_t TicksAtAccessoryRelease = 0;
static void trigger_press_cb(void *arg, void *usr_data)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
TicksAtTriggerPress = xTaskGetTickCountFromISR();
KEvent_T switch_event = {.ID = KEVENT_TRIGGER_SWITCH_PRESSED, .Data = NULL};
Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}
static void trigger_release_cb(void *arg, void *usr_data)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
uint32_t triggerPressDurationInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtTriggerPress);
KEvent_T switch_event = {.ID = KEVENT_TRIGGER_SWITCH_RELEASED, .Data = (void *) triggerPressDurationInms};
Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}
static void accessory_press_cb(void *arg, void *usr_data)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
TicksAtAccessoryPress = xTaskGetTickCountFromISR();
uint32_t accessoryTimeReleasedInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtAccessoryRelease);
KEvent_T switch_event = {.ID = KEVENT_ACCESSORY_SWITCH_PRESSED, .Data = (void *) accessoryTimeReleasedInms};
Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}
static void accessory_release_cb(void *arg, void *usr_data)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
TicksAtAccessoryRelease = xTaskGetTickCountFromISR();
uint32_t accessoryPressDurationInms = pdTICKS_TO_MS(xTaskGetTickCountFromISR() - TicksAtAccessoryPress);
KEvent_T switch_event = {.ID = KEVENT_ACCESSORY_SWITCH_RELEASED, .Data = (void *) accessoryPressDurationInms};
Post_KEvent_From_ISR(&switch_event, &xHigherPriorityTaskWoken);
}
void Initialize_Switches(void)
{
KLOG_INFO(TAG, "Initializing Switches...");
Trigger_Button = iot_button_create(&Trigger_Button_Config);
assert(Trigger_Button);
Accessory_Button = iot_button_create(&Accessory_Button_Config);
assert(Accessory_Button);
iot_button_register_cb(Trigger_Button, BUTTON_PRESS_DOWN, trigger_press_cb, NULL);
iot_button_register_cb(Trigger_Button, BUTTON_PRESS_UP, trigger_release_cb, NULL);
iot_button_register_cb(Accessory_Button, BUTTON_PRESS_DOWN, accessory_press_cb, NULL);
iot_button_register_cb(Accessory_Button, BUTTON_PRESS_UP, accessory_release_cb, NULL);
}

View file

@ -0,0 +1,2 @@
void Initialize_Switches(void);

1
components/SystemK Submodule

@ -0,0 +1 @@
Subproject commit 6f51f5b006db81d75dd752456be264bc50ce1b9b

View file

@ -0,0 +1,20 @@
idf_component_register(
SRCS
"WiFi.c"
INCLUDE_DIRS
"."
REQUIRES
"SystemK"
"esp_wifi"
"nvs_flash"
"esp_netif"
"esp_http_client"
"esp_event"
"esp_system"
"freertos"
"esp-tls"
"wifi_provisioning"
"esp_https_ota"
"app_update"
"NVM"
)

253
components/WiFi/WiFi.c Normal file
View file

@ -0,0 +1,253 @@
#include <SystemK.h>
#include <USB.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_event.h"
#include <string.h>
#include "esp_wifi.h"
#include "esp_http_client.h"
#include "esp_tls.h"
#include "esp_https_ota.h"
#include "esp_ota_ops.h"
#include "esp_crt_bundle.h"
#include "USB.h"
#include "Key_Value.h"
static const char *TAG = "WiFi";
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define KTAG_WIFI_TASK_PRIORITY 2
static TaskHandle_t xWiFiTask = NULL;
static StaticTask_t xWiFiTaskBuffer;
static StackType_t xWiFiTaskStack[4096];
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
KLOG_INFO(TAG, "Connecting to the WiFi Access Point...");
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED)
{
KLOG_INFO(TAG, "Connected to the WiFi Access Point!");
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
KLOG_WARN(TAG, "Failed to connect to the WiFi Access Point!");
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
KLOG_INFO(TAG, "Got an IP address:" IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
else
{
KLOG_WARN(TAG, "Unhandled event: %s %ld", event_base, event_id);
}
}
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id)
{
case HTTP_EVENT_ERROR:
KLOG_DEBUG(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
KLOG_DEBUG(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
KLOG_DEBUG(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
KLOG_DEBUG(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_DATA:
KLOG_DEBUG(TAG, "HTTP_EVENT_ON_DATA, %.*s", evt->data_len, (char *)evt->data);
break;
case HTTP_EVENT_ON_FINISH:
KLOG_DEBUG(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
KLOG_DEBUG(TAG, "HTTP_EVENT_DISCONNECTED");
break;
case HTTP_EVENT_REDIRECT:
KLOG_DEBUG(TAG, "HTTP_EVENT_REDIRECT");
break;
}
return ESP_OK;
}
bool wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.failure_retry_cnt = 3,
},
};
char STA_SSID[32];
char STA_Password[64];
KV_Get_Value_string(CONFIG_FILE, "STA_SSID", STA_SSID);
KV_Get_Value_string(CONFIG_FILE, "STA_Password", STA_Password);
strlcpy((char *)wifi_config.sta.ssid, STA_SSID, sizeof(wifi_config.sta.ssid));
strlcpy((char *)wifi_config.sta.password, STA_Password, sizeof(wifi_config.sta.password));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
// Wait until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
// number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above).
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT)
{
KLOG_INFO(TAG, "Connected to AP with SSID: %s", wifi_config.sta.ssid);
return true;
}
else if (bits & WIFI_FAIL_BIT)
{
KLOG_WARN(TAG, "Failed to connect to SSID \"%s\"!", wifi_config.sta.ssid);
return false;
}
else
{
KLOG_ERROR(TAG, "Unexpected event!");
return false;
}
}
void download_test_file(void)
{
esp_http_client_config_t config = {
.url = "https://ktag.clubk.club/2024A.txt",
.crt_bundle_attach = esp_crt_bundle_attach,
.event_handler = _http_event_handler};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK)
{
KLOG_INFO(TAG, "HTTPS Status = %d, content_length = %lld",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
}
else
{
KLOG_ERROR(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
esp_http_client_config_t ota_http_config = {
.crt_bundle_attach = esp_crt_bundle_attach,
.event_handler = _http_event_handler,
.buffer_size = 8192,
.keep_alive_enable = true,
.timeout_ms = 30 * 1000};
esp_https_ota_config_t ota_config = {
.http_config = &ota_http_config,
.bulk_flash_erase = false,
};
static void WiFi_Task(void *pvParameters)
{
if (wifi_init_sta())
{
download_test_file();
vTaskDelay(5000 / portTICK_PERIOD_MS);
const esp_partition_t *running = esp_ota_get_running_partition();
esp_app_desc_t running_app_info;
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK)
{
KLOG_INFO(TAG, "Running firmware version: %s", running_app_info.version);
}
ota_http_config.url = Get_OTA_Image_URL();
KLOG_INFO(TAG, "Attempting OTA reprogramming from %s", ota_http_config.url);
esp_log_level_set("wifi", ESP_LOG_VERBOSE);
esp_err_t err = esp_https_ota(&ota_config);
if (err == ESP_OK)
{
Erase_OTA_File();
KLOG_INFO(TAG, "OTA Succeed, Rebooting...\n");
vTaskDelay(5000 / portTICK_PERIOD_MS);
esp_restart();
}
else
{
KLOG_ERROR(TAG, "OTA Failed!\n");
}
}
vTaskDelete(NULL);
}
void Initialize_WiFi(void)
{
if (xWiFiTask == NULL)
{
KLOG_INFO(TAG, "Initializing WiFi...");
xWiFiTask = xTaskCreateStaticPinnedToCore(
WiFi_Task,
"KTag WiFI",
sizeof(xWiFiTaskStack) / sizeof(StackType_t),
NULL,
KTAG_WIFI_TASK_PRIORITY,
xWiFiTaskStack,
&xWiFiTaskBuffer,
APP_CPU_NUM);
}
else
{
KLOG_WARN(TAG, "WiFi already running.");
}
}

1
components/WiFi/WiFi.h Normal file
View file

@ -0,0 +1 @@
void Initialize_WiFi(void);

87
dependencies.lock Normal file
View file

@ -0,0 +1,87 @@
dependencies:
chmorgan/esp-audio-player:
component_hash: c8ac1998e9af863bc41b57e592f88d1a5791a0f891485122336ddabbf7a65033
dependencies:
- name: idf
require: private
version: '>=5.0'
- name: chmorgan/esp-libhelix-mp3
registry_url: https://components.espressif.com
require: private
version: '>=1.0.0,<2.0.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.7
chmorgan/esp-libhelix-mp3:
component_hash: cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53
dependencies:
- name: idf
require: private
version: '>=4.1.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.3
espressif/button:
component_hash: 30a3f495c3862d505ce6e41adbbd218b2750e9723ab2151feff00e9fe685b326
dependencies:
- name: espressif/cmake_utilities
registry_url: https://components.espressif.com
require: private
version: 0.*
- name: idf
require: private
version: '>=4.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 3.5.0
espressif/cmake_utilities:
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 0.5.3
espressif/mdns:
component_hash: d36b265164be5139f92de993f08f5ecaa0de0c0acbf84deee1f10bb5902d04ff
dependencies:
- name: idf
require: private
version: '>=5.0'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.4.3
espressif/usb_host_msc:
component_hash: efbf44743b0f1f1f808697a671064531ae4661ccbce84632637261f8f670b375
dependencies:
- name: idf
require: private
version: '>=4.4.1'
source:
registry_url: https://components.espressif.com/
type: service
targets:
- esp32s2
- esp32s3
- esp32p4
version: 1.1.3
idf:
source:
type: idf
version: 5.4.0
direct_dependencies:
- chmorgan/esp-audio-player
- chmorgan/esp-libhelix-mp3
- espressif/button
- espressif/mdns
- espressif/usb_host_msc
- idf
manifest_hash: 49abffad73ef20c1e9924d5aece4befeab0cbad25b99f353334b089de1f63639
target: esp32s3
version: 2.0.0

14
main/CMakeLists.txt Normal file
View file

@ -0,0 +1,14 @@
idf_component_register(
SRCS
"HW_NeoPixels.c"
"main.c"
INCLUDE_DIRS
"."
)
# Create a SPIFFS image from the contents of the 'spiffs_image' directory
# that fits the partition named 'spiffs'. FLASH_IN_PROJECT indicates that
# the generated image should be flashed when the entire project is flashed to
# the target with 'idf.py -p PORT flash'.
#spiffs_create_partition_image(spiffs ../spiffs_image FLASH_IN_PROJECT)
spiffs_create_partition_image(spiffs ../spiffs_image)

230
main/HW_NeoPixels.c Normal file
View file

@ -0,0 +1,230 @@
#include <SystemK.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string.h>
#include <muxed_led_strip.h>
#include <muxed_led_strip_interface.h>
#include <driver/gpio.h>
#include <driver/rmt_tx.h>
#include "HW_NeoPixels.h"
#define NEOPIXELS_STACK_SIZE (100 * 1024)
#define NEOPIXELS_TASK_PRIORITY (tskIDLE_PRIORITY + 1)
static StaticTask_t xTaskBuffer;
StackType_t *xStack;
static TaskHandle_t xTaskHandle;
static muxed_led_strip_handle_t NeoPixel_Out;
// GPIO assignments
#define NEOPIXEL_OUT_GPIO GPIO_NUM_48
#define BARREL_ENABLE_GPIO GPIO_NUM_18
#define RECEIVER_ENABLE_GPIO GPIO_NUM_45
#define DISPLAY_ENABLE_GPIO GPIO_NUM_10
#define EFFECTS_ENABLE_GPIO GPIO_NUM_17
// 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define LED_STRIP_RMT_RES_HZ (10 * 1000 * 1000)
static const char *TAG = "NeoPixels";
static inline void Enable_Channel(NeoPixelsChannel_T channel)
{
// The Enable lines are active low.
if (channel == NEOPIXEL_CHANNEL_ALL)
{
gpio_set_level(BARREL_ENABLE_GPIO, 0);
gpio_set_level(RECEIVER_ENABLE_GPIO, 0);
gpio_set_level(DISPLAY_ENABLE_GPIO, 0);
gpio_set_level(EFFECTS_ENABLE_GPIO, 0);
}
else if (channel < CONFIG_KTAG_N_NEOPIXEL_CHANNELS)
{
gpio_set_level(BARREL_ENABLE_GPIO, channel == NEOPIXEL_CHANNEL_BARREL ? 0 : 1);
gpio_set_level(RECEIVER_ENABLE_GPIO, channel == NEOPIXEL_CHANNEL_RECEIVER ? 0 : 1);
gpio_set_level(DISPLAY_ENABLE_GPIO, channel == NEOPIXEL_CHANNEL_DISPLAY ? 0 : 1);
gpio_set_level(EFFECTS_ENABLE_GPIO, channel == NEOPIXEL_CHANNEL_EFFECTS ? 0 : 1);
}
else
{
// Select none.
gpio_set_level(BARREL_ENABLE_GPIO, 1);
gpio_set_level(RECEIVER_ENABLE_GPIO, 1);
gpio_set_level(DISPLAY_ENABLE_GPIO, 1);
gpio_set_level(EFFECTS_ENABLE_GPIO, 1);
}
}
static bool IRAM_ATTR rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *edata, void *user_data)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Send a notification to the task that the current channel is complete.
xTaskNotifyIndexedFromISR(xTaskHandle,
0,
0,
eSetBits,
&xHigherPriorityTaskWoken);
return xHigherPriorityTaskWoken;
}
static rmt_tx_event_callbacks_t cbs = {
.on_trans_done = rmt_tx_done_callback,
};
static gpio_config_t barrel_enable_gpio_config = {
.pin_bit_mask = (1ULL << BARREL_ENABLE_GPIO),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
static gpio_config_t receiver_enable_gpio_config = {
.pin_bit_mask = (1ULL << RECEIVER_ENABLE_GPIO),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
static gpio_config_t display_enable_gpio_config = {
.pin_bit_mask = (1ULL << DISPLAY_ENABLE_GPIO),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
static gpio_config_t effects_enable_gpio_config = {
.pin_bit_mask = (1ULL << EFFECTS_ENABLE_GPIO),
.mode = GPIO_MODE_OUTPUT, // Set mode to output
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE};
static muxed_led_strip_config_t neopixel_out_config = {
.strip_gpio_num = NEOPIXEL_OUT_GPIO, // The GPIO that connected to the LED strip's data line
.channels = CONFIG_KTAG_N_NEOPIXEL_CHANNELS, // The number of multiplexed channels
.max_leds = CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL, // The number of LEDs in the strip
.led_pixel_format = LED_PIXEL_FORMAT_GRB, // Pixel format of your LED strip
.led_model = LED_MODEL_WS2812, // LED strip model
.flags.invert_out = false, // whether to invert the output signal
};
// From <https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf#rmt>:
//
// The RMT module has eight channels, numbered from zero to seven. Each channel is able to independently
// transmit or receive signals.
// • Channel 0 ~ 3 (TX channel) are dedicated to sending signals.
// • Channel 4 ~ 7 (RX channel) are dedicated to receiving signals.
// Each TX/RX channel is controlled by a dedicated set of registers with the same functionality. Channel 3 and
// channel 7 support DMA access, so the two channels also have a set of DMA-related control and status registers
static led_strip_rmt_config_t rmt_config_noDMA = {
.clk_src = RMT_CLK_SRC_XTAL, // different clock source can lead to different power consumption
.resolution_hz = LED_STRIP_RMT_RES_HZ, // RMT counter clock frequency
.flags.with_dma = false, // Only the last channel has the DMA capability
};
static led_strip_spi_config_t spi_config_withDMA __attribute__((unused)) = {
.clk_src = RMT_CLK_SRC_XTAL, // different clock source can lead to different power consumption
.flags.with_dma = true, // Using DMA can improve performance and help drive more LEDs
.spi_bus = SPI2_HOST, // SPI bus ID
};
void Initialize_SystemK_NeoPixels(SemaphoreHandle_t init_complete)
{
xStack = (uint8_t *)heap_caps_calloc(1, NEOPIXELS_STACK_SIZE, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT | MALLOC_CAP_32BIT);
// Create the task without using any dynamic memory allocation.
xTaskHandle = xTaskCreateStaticPinnedToCore(
NeoPixels_Task, // Function that implements the task (this is part of SystemK).
"NeoPixels", // Text name for the task.
NEOPIXELS_STACK_SIZE, // Number of indexes in the xStack array.
NULL, // Parameter passed into the task.
NEOPIXELS_TASK_PRIORITY, // Priority at which the task is created.
xStack, // Array to use as the task's stack.
&xTaskBuffer, // Variable to hold the task's data structure.
APP_CPU_NUM); // Specify the task's core affinity.
KLOG_INFO(TAG, "Initialization complete.");
xSemaphoreGive(init_complete);
}
SystemKResult_T HW_NeoPixels_Init(void)
{
if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 0)
{
// Initialize the NeoPixel Out and the Barrel Enable.
ESP_ERROR_CHECK(muxed_led_strip_new_rmt_device(&neopixel_out_config, &rmt_config_noDMA, &cbs, &NeoPixel_Out));
KLOG_INFO(TAG, "Initialized NeoPixel Out as GPIO[%d].", NEOPIXEL_OUT_GPIO);
ESP_ERROR_CHECK(gpio_config(&barrel_enable_gpio_config));
KLOG_INFO(TAG, "Initialized barrel NeoPixel enable as GPIO[%d].", BARREL_ENABLE_GPIO);
}
if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 1)
{
ESP_ERROR_CHECK(gpio_config(&receiver_enable_gpio_config));
KLOG_INFO(TAG, "Initialized receiver NeoPixel enable as GPIO[%d].", RECEIVER_ENABLE_GPIO);
}
if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 2)
{
ESP_ERROR_CHECK(gpio_config(&display_enable_gpio_config));
KLOG_INFO(TAG, "Initialized display NeoPixel enable as GPIO[%d].", DISPLAY_ENABLE_GPIO);
}
if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS > 3)
{
ESP_ERROR_CHECK(gpio_config(&effects_enable_gpio_config));
KLOG_INFO(TAG, "Initialized effects NeoPixel enable as GPIO[%d].", EFFECTS_ENABLE_GPIO);
}
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T HW_NeoPixels_Set_RGB(NeoPixelsChannel_T channel, uint8_t position, uint8_t red, uint8_t green, uint8_t blue)
{
if (channel < CONFIG_KTAG_N_NEOPIXEL_CHANNELS)
{
// TODO: Add code to account for RGB order in each channel.
ESP_ERROR_CHECK(muxed_led_strip_set_pixel(NeoPixel_Out, channel, position, red, green, blue));
}
return SYSTEMK_RESULT_SUCCESS;
}
SystemKResult_T HW_NeoPixels_Publish(void)
{
for (uint_fast8_t channel = 0; channel < CONFIG_KTAG_N_NEOPIXEL_CHANNELS; channel++)
{
uint32_t channel_complete;
Enable_Channel(channel);
ESP_ERROR_CHECK_WITHOUT_ABORT(muxed_led_strip_refresh(NeoPixel_Out, channel));
xTaskNotifyWaitIndexed(0, /* Wait for 0th Notification */
0x00, /* Don't clear any bits on entry. */
UINT32_MAX, /* Clear all bits on exit. */
&channel_complete, /* Receives the notification value. */
portMAX_DELAY); /* Block indefinitely. */
// KLOG_INFO(TAG, "Published %lu.", channel_complete);
}
Enable_Channel(NEOPIXEL_CHANNEL_NONE);
return SYSTEMK_RESULT_SUCCESS;
}
color_t HW_NeoPixels_Get_My_Color(void)
{
color_t result = COLOR_ORANGE;
uint8_t Team_ID;
uint8_t Player_ID;
uint8_t Weapon_ID;
(void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_TEAMID, &Team_ID);
(void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_PLAYERID, &Player_ID);
(void) SETTINGS_get_uint8_t(SYSTEMK_SETTING_WEAPONID, &Weapon_ID);
result = PROTOCOLS_GetColor(GetWeaponFromID(Weapon_ID).Protocol, Team_ID, Player_ID);
return result;
}

6
main/HW_NeoPixels.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef HW_NEOPIXELS_H
#define HW_NEOPIXELS_H
void Initialize_SystemK_NeoPixels(SemaphoreHandle_t init_complete);
#endif // HW_NEOPIXELS_H

11
main/Version.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION_MAJOR 00
#define VERSION_MINOR 38
#define STRINGIFY(number) #number
#define VERSION_STRING(major, minor) STRINGIFY(major) "." STRINGIFY(minor)
#define VERSION_AS_STR() "Version " VERSION_STRING(VERSION_MAJOR, VERSION_MINOR) " " __DATE__ " " __TIME__
#endif // VERSION_H

24
main/idf_component.yml Normal file
View file

@ -0,0 +1,24 @@
## IDF Component Manager Manifest File
dependencies:
chmorgan/esp-libhelix-mp3: "^1.0.3"
chmorgan/esp-audio-player: "^1.0.7"
espressif/button: "^3.3.1"
espressif/mdns: "^1.3.2"
espressif/usb_host_msc: "^1.1.2"
## Required IDF version (>=5.1 is required for the SPI backend of the led-strip component.)
## We tested with 5.4.0.
idf:
version: ">=5.4.0"
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true

140
main/main.c Normal file
View file

@ -0,0 +1,140 @@
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKky+.`/ykKKKKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKds/. -+o:` ./sdNKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKNds+-` `-+hNKKKKNho:` `-+shNKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKNkhyo+:. `-/sdNKKKKKKKKKKKKKky+:` .-/oyhdNKKKKKKKKKK
// KKys++:-.````.-:+oykNKKKKKKKKKKKKKKKKKKKKKKNkhs+/-.````.-:/+syKK
// KK -/+osydkNNNKKKkkkkkkkNKKKKKKKKKKKkkkkkkkkNKKKKNNkdhyso/: KK
// KK sKKKKKKKKKKKKK```````/KKKKKKKKKd-```````:kKKKKKKKKKKKKKd `KK
// KK- oKKKKKKKKKKKKK :KKKKKKKKo` `oNKKKKKKKKKKKKKKh :KK
// KK/ +KKKKKKKKKKKKK :KKKKKKd- -dKKKKKKKKKKKKKKKKy /KK
// KK+ /KKKKKKKKKKKKK :KKKKKs` +NKKKKKKKKKKKKKKKKKs +KK
// KKo :KKKKKKKKKKKKK :KKKk: .hKKKKKKKKKKKKKKKKKKKo oKK
// KKy -KKKKKKKKKKKKK :KKy` +NKKKKKKKKKKKKKKKKKKKK/ yKK
// KKd `KKKKKKKKKKKKK :k/ .hKKKKKKKKKKKKKKKKKKKKKK: dKK
// KKN NKKKKKKKKKKKK .. /kKKKKKKKKKKKKKKKKKKKKKKK. NKK
// KKK. dKKKKKKKKKKKK .yKKKKKKKKKKKKKKKKKKKKKKKKN .KKK
// KKK+ oKKKKKKKKKKKK -kKKKKKKKKKKKKKKKKKKKKKKKKKh +KKK
// KKKd .KKKKKKKKKKKK `sNKKKKKKKKKKKKKKKKKKKKKKKK/ dKKK
// KKKK: hKKKKKKKKKKK :kKKKKKKKKKKKKKKKKKKKKKKk :KKKK
// KKKKh -KKKKKKKKKKK `` .yKKKKKKKKKKKKKKKKKKKKK+ hKKKK
// KKKKK/ yKKKKKKKKKK T :d: /kKKKKKKKKKKKKKKKKKKk`:KKKKK
// KKKKKk`.NKKKKKKKKK :KNo` .hKKKKKKKKKKKKKKKKK:`kKKKKK
// KKKKKKy /KKKKKKKKK A :KKKd- +NKKKKKKKKKKKKKKo yKKKKKK
// KKKKKKK+ oKKKKKKKK :KKKKN+` -hKKKKKKKKKKKKy`+KKKKKKK
// KKKKKKKN/ sKKKKKKK G :KKKKKKh. `oNKKKKKKKKKh`/KKKKKKKK
// KKKKKKKKN/`sKKKKKK :KKKKKKKN/ -dKKKKKKKh`/NKKKKKKKK
// KKKKKKKKKK+ +NKKKK :KKKKKKKKKy. `sNKKKKs`+KKKKKKKKKK
// KKKKKKKKKKKs`:kKKK-------+KKKKKKKKKKk/--------oKKN+`sKKKKKKKKKKK
// KKKKKKKKKKKKh..yKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKd--dKKKKKKKKKKKK
// KKKKKKKKKKKKKN+`/kKKKKKKKKKKKKKKKKKKKKKKKKKKKKNo`+NKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKh-`sNKKKKKKKKKKKKKKKKKKKKKKKKNy.-hKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKs..sNKKKKKKKKKKKKKKKKKKKKNy-.yKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKNs..okKKKKKKKKKKKKKKKKNs-.sNKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKy-`/hKKKKKKKKKKKKd+`-yKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKd/`.odKKKKKKks-`/dKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKNs: .+yy+-`:sNKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKNy/..+yNKKKKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
// KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
#include <string.h>
#include <SystemK.h>
#include <SPIFFS.h>
#include <USB.h>
#include <I2S_Audio.h>
#include <Switches.h>
#include <BLE.h>
#include <IR.h>
#include <WiFi.h>
#include "nvs_flash.h"
#include "HW_NeoPixels.h"
#include "Version.h"
#include "Reprogramming.h"
static const char *TAG = "KTag 2024A";
static SemaphoreHandle_t init_complete_semaphore;
static const uint16_t INITIALIZATION_TIMEOUT_IN_ms = 10 * 1000;
void app_main(void)
{
KLOG_INFO(TAG, VERSION_AS_STR());
KLOG_INFO(TAG, "Initializing app...");
init_complete_semaphore = xSemaphoreCreateBinary();
// Initialize NVS — it is used by both the BLE and WiFi drivers.
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
Initialize_SPIFFS(init_complete_semaphore);
if (xSemaphoreTake(init_complete_semaphore, pdMS_TO_TICKS(INITIALIZATION_TIMEOUT_IN_ms)) != pdTRUE)
{
KLOG_ERROR(TAG, "Timeout initializing SPIFFS!");
}
Initialize_USB(init_complete_semaphore);
if (xSemaphoreTake(init_complete_semaphore, pdMS_TO_TICKS(INITIALIZATION_TIMEOUT_IN_ms)) != pdTRUE)
{
KLOG_ERROR(TAG, "Timeout initializing USB!");
}
if (OTA_File_Exists() == true)
{
KLOG_INFO(TAG, "Attempting OTA reprogramming from %s.", Get_OTA_Image_URL());
KLOG_WARN(TAG, "This does not work reliably...use USB.");
Initialize_WiFi();
}
else
{
Initialize_Audio();
Initialize_SystemK_NeoPixels(init_complete_semaphore);
if (xSemaphoreTake(init_complete_semaphore, pdMS_TO_TICKS(INITIALIZATION_TIMEOUT_IN_ms)) != pdTRUE)
{
KLOG_ERROR(TAG, "Timeout initializing NeoPixels!");
}
Initialize_BLE();
Initialize_IR(init_complete_semaphore);
if (xSemaphoreTake(init_complete_semaphore, pdMS_TO_TICKS(INITIALIZATION_TIMEOUT_IN_ms)) != pdTRUE)
{
KLOG_ERROR(TAG, "Timeout initializing IR!");
}
if (Initialize_SystemK() != SYSTEMK_RESULT_SUCCESS)
{
KLOG_ERROR(TAG, "Error initializing SystemK!");
}
// Initialize the switches after SystemK, so xQueueEvents will have already been created.
Initialize_Switches();
vSemaphoreDelete(init_complete_semaphore);
KLOG_INFO(TAG, "Initialization complete.");
}
while (true)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
SystemKResult_T HW_Execute_Console_Command(const uint8_t *const command)
{
SystemKResult_T result = SYSTEMK_RESULT_NOT_IMPLEMENTED;
if (strncmp((const char *)command, "reprogram", 9) == 0)
{
Request_Reprogramming_From_USB();
result = SYSTEMK_RESULT_SUCCESS;
}
return result;
}

View file

@ -0,0 +1 @@
c8ac1998e9af863bc41b57e592f88d1a5791a0f891485122336ddabbf7a65033

View file

@ -0,0 +1,27 @@
set(srcs
"audio_player.cpp"
)
set(includes
"include"
)
set(requires "")
if(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
list(APPEND srcs "audio_mp3.cpp")
endif()
# TODO: move inside of the 'if(CONFIG_AUDIO_PLAYER_ENABLE_MP3)' when everything builds correctly
list(APPEND requires "esp-libhelix-mp3")
if(CONFIG_AUDIO_PLAYER_ENABLE_WAV)
list(APPEND srcs "audio_wav.cpp")
endif()
idf_component_register(SRCS "${srcs}"
REQUIRES "${requires}"
INCLUDE_DIRS "${includes}"
REQUIRES driver
)

View file

@ -0,0 +1,20 @@
menu "Audio playback"
config AUDIO_PLAYER_ENABLE_MP3
bool "Enable mp3 decoding."
default y
help
The audio player can play mp3 files using libhelix-mp3.
config AUDIO_PLAYER_ENABLE_WAV
bool "Enable wav file playback"
default y
help
Audio player can decode wave files.
config AUDIO_PLAYER_LOG_LEVEL
int "Audio Player log level (0 none - 3 highest)"
default 0
range 0 3
help
Specify the verbosity of Audio Player log output.
endmenu

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,80 @@
# Audio player component for esp32
[![cppcheck-action](https://github.com/chmorgan/esp-audio-player/actions/workflows/cppcheck.yml/badge.svg)](https://github.com/chmorgan/esp-audio-player/actions/workflows/cppcheck.yml)
## Capabilities
* MP3 decoding (via libhelix-mp3)
* Wav/wave file decoding
## Who is this for?
Decode only audio playback on esp32 series of chips, where the features and footprint of esp-adf are not
necessary.
## What about esp-adf?
This component is not intended to compete with esp-adf, a much more fully developed
audio framework.
It does however have a number of advantages at the moment including:
* Fully open source (esp-adf has a number of binary modules at the moment)
* Minimal size (it's less capable, but also simpler, than esp-adf)
## Getting started
### Examples
* [esp-box mp3_demo](https://github.com/espressif/esp-box/tree/master/examples/mp3_demo) uses esp-audio-player.
* The [test example](https://github.com/chmorgan/esp-audio-player/tree/main/test) is a simpler example than mp3_demo that also uses the esp-box hardware.
### How to use this?
[esp-audio-player is a component](https://components.espressif.com/components/chmorgan/esp-audio-player) on the [Espressif component registry](https://components.espressif.com).
In your project run:
```
idf.py add-dependency chmorgan/esp-audio-player
```
to add the component dependency to the project's manifest file.
## Dependencies
For MP3 support you'll need the [esp-libhelix-mp3](https://github.com/chmorgan/esp-libhelix-mp3) component.
## Tests
Unity tests are implemented in the [test/](../test) folder.
## States
```mermaid
stateDiagram-v2
[*] --> Idle : new(), cb(IDLE)
Idle --> Playing : play(), cb(PLAYING)
Playing --> Paused : pause(), cb(PAUSE)
Paused --> Playing : resume(), cb(PLAYING)
Playing --> Playing : play(), cb(COMPLETED_PLAYING_NEXT)
Paused --> Idle : stop(), cb(IDLE)
Playing --> Idle : song complete, cb(IDLE)
[*] --> Shutdown : delete(), cb(SHUTDOWN)
Shutdown --> Idle : new(), cb(IDLE)
```
Note: Diagram shortens callbacks from AUDIO_PLAYER_EVENT_xxx to xxx, and functions from audio_player_xxx() to xxx(), for clarity.
## Release process - Pushing component to the IDF Component Registry
The github workflow, .github/workflows/esp_upload_component.yml, pushes data to the espressif
[IDF component registry](https://components.espressif.com).
To push a new version:
* Apply a git tag via 'git tag vA.B.C'
* Push tags via 'git push --tags'
The github workflow *should* run and automatically push to the IDF component registry.

View file

@ -0,0 +1,57 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
typedef enum {
DECODE_STATUS_CONTINUE, /*< data remaining, call decode again */
DECODE_STATUS_NO_DATA_CONTINUE, /*< data remaining but none in this call */
DECODE_STATUS_DONE, /*< no data remaining to decode */
DECODE_STATUS_ERROR /*< unrecoverable error */
} DECODE_STATUS;
typedef struct {
int sample_rate;
uint32_t bits_per_sample;
uint32_t channels;
} format;
/**
* Decoded audio data ready for playback
*
* Fields in this structure are expected to be updated
* upon each cycle of the decoder, as the decoder stores
* audio data to be played back.
*/
typedef struct {
/**
* NOTE: output_samples is flushed each decode cycle
*
* NOTE: the decode format determines how to convert samples to frames, ie.
* whether these samples are stero or mono samples and what the bits per sample are
*/
uint8_t *samples;
/** capacity of samples */
size_t samples_capacity;
/**
* 2x samples_capacity to allow for in-place conversion of
* mono to stereo
*/
size_t samples_capacity_max;
/**
* Number of frames in samples,
* Note that each frame consists of 'fmt.channels' number of samples,
* for example for stereo output the number of samples is 2x the
* frame count.
*/
size_t frame_count;
format fmt;
} decode_data;
#define BYTES_IN_WORD 2
#define BITS_PER_BYTE 8

View file

@ -0,0 +1,26 @@
#pragma once
#include "esp_log.h"
#if CONFIG_AUDIO_PLAYER_LOG_LEVEL >= 1
#define LOGI_1(FMT, ...) \
ESP_LOGI(TAG, "[1] " FMT, ##__VA_ARGS__)
#else
#define LOGI_1(FMT, ...) { (void)TAG; }
#endif
#if CONFIG_AUDIO_PLAYER_LOG_LEVEL >= 2
#define LOGI_2(FMT, ...) \
ESP_LOGI(TAG, "[2] " FMT, ##__VA_ARGS__)
#else
#define LOGI_2(FMT, ...) { (void)TAG;}
#endif
#if CONFIG_AUDIO_PLAYER_LOG_LEVEL >= 3
#define LOGI_3(FMT, ...) \
ESP_LOGI(TAG, "[3] " FMT, ##__VA_ARGS__)
#define COMPILE_3(x) x
#else
#define LOGI_3(FMT, ...) { (void)TAG; }
#define COMPILE_3(x) {}
#endif

View file

@ -0,0 +1,169 @@
#include <string.h>
#include "audio_log.h"
#include "audio_mp3.h"
static const char *TAG = "mp3";
bool is_mp3(FILE *fp) {
bool is_mp3_file = false;
fseek(fp, 0, SEEK_SET);
// see https://en.wikipedia.org/wiki/List_of_file_signatures
uint8_t magic[3];
if(sizeof(magic) == fread(magic, 1, sizeof(magic), fp)) {
if((magic[0] == 0xFF) &&
(magic[1] == 0xFB))
{
is_mp3_file = true;
} else if((magic[0] == 0xFF) &&
(magic[1] == 0xF3))
{
is_mp3_file = true;
} else if((magic[0] == 0xFF) &&
(magic[1] == 0xF2))
{
is_mp3_file = true;
} else if((magic[0] == 0x49) &&
(magic[1] == 0x44) &&
(magic[2] == 0x33)) /* 'ID3' */
{
fseek(fp, 0, SEEK_SET);
/* Get ID3 head */
mp3_id3_header_v2_t tag;
if (sizeof(mp3_id3_header_v2_t) == fread(&tag, 1, sizeof(mp3_id3_header_v2_t), fp)) {
if (memcmp("ID3", (const void *) &tag, sizeof(tag.header)) == 0) {
is_mp3_file = true;
}
}
}
}
// seek back to the start of the file to avoid
// missing frames upon decode
fseek(fp, 0, SEEK_SET);
return is_mp3_file;
}
/**
* @return true if data remains, false on error or end of file
*/
DECODE_STATUS decode_mp3(HMP3Decoder mp3_decoder, FILE *fp, decode_data *pData, mp3_instance *pInstance) {
MP3FrameInfo frame_info;
size_t unread_bytes = pInstance->bytes_in_data_buf - (pInstance->read_ptr - pInstance->data_buf);
/* somewhat arbitrary trigger to refill buffer - should always be enough for a full frame */
if (unread_bytes < 1.25 * MAINBUF_SIZE && !pInstance->eof_reached) {
uint8_t *write_ptr = pInstance->data_buf + unread_bytes;
size_t free_space = pInstance->data_buf_size - unread_bytes;
/* move last, small chunk from end of buffer to start,
then fill with new data */
memmove(pInstance->data_buf, pInstance->read_ptr, unread_bytes);
size_t nRead = fread(write_ptr, 1, free_space, fp);
pInstance->bytes_in_data_buf = unread_bytes + nRead;
pInstance->read_ptr = pInstance->data_buf;
if ((nRead == 0) || feof(fp)) {
pInstance->eof_reached = true;
}
LOGI_2("pos %ld, nRead %d, eof %d", ftell(fp), nRead, pInstance->eof_reached);
unread_bytes = pInstance->bytes_in_data_buf;
}
LOGI_3("data_buf 0x%p, read 0x%p", pInstance->data_buf, pInstance->read_ptr);
if(unread_bytes == 0) {
LOGI_1("unread_bytes == 0, status done");
return DECODE_STATUS_DONE;
}
/* Find MP3 sync word from read buffer */
int offset = MP3FindSyncWord(pInstance->read_ptr, unread_bytes);
LOGI_2("unread %d, total %d, offset 0x%x(%d)",
unread_bytes, pInstance->bytes_in_data_buf, offset, offset);
if (offset >= 0) {
COMPILE_3(int starting_unread_bytes = unread_bytes);
uint8_t *read_ptr = pInstance->read_ptr + offset; /*!< Data start point */
unread_bytes -= offset;
LOGI_3("read 0x%p, unread %d", read_ptr, unread_bytes);
int mp3_dec_err = MP3Decode(mp3_decoder, &read_ptr, (int*)&unread_bytes, reinterpret_cast<int16_t *>(pData->samples),
0);
pInstance->read_ptr = read_ptr;
if(mp3_dec_err == ERR_MP3_NONE) {
/* Get MP3 frame info */
MP3GetLastFrameInfo(mp3_decoder, &frame_info);
pData->fmt.sample_rate = frame_info.samprate;
pData->fmt.bits_per_sample = frame_info.bitsPerSample;
pData->fmt.channels = frame_info.nChans;
pData->frame_count = (frame_info.outputSamps / frame_info.nChans);
LOGI_3("mp3: channels %d, sr %d, bps %d, frame_count %d, processed %d",
pData->fmt.channels,
pData->fmt.sample_rate,
pData->fmt.bits_per_sample,
frame_info.outputSamps,
starting_unread_bytes - unread_bytes);
} else {
if (pInstance->eof_reached) {
ESP_LOGE(TAG, "status error %d, but EOF", mp3_dec_err);
return DECODE_STATUS_DONE;
} else if (mp3_dec_err == ERR_MP3_MAINDATA_UNDERFLOW) {
// underflow indicates MP3Decode should be called again
LOGI_1("underflow read ptr is 0x%p", read_ptr);
return DECODE_STATUS_NO_DATA_CONTINUE;
} else {
// NOTE: some mp3 files result in misdetection of mp3 frame headers
// and during decode these misdetected frames cannot be
// decoded
//
// Rather than give up on the file by returning
// DECODE_STATUS_ERROR, we ask the caller
// to continue to call us, by returning DECODE_STATUS_NO_DATA_CONTINUE.
//
// The invalid frame data is skipped over as a search for the next frame
// on the subsequent call to this function will start searching
// AFTER the misdetected frmame header, dropping the invalid data.
//
// We may want to consider a more sophisticated approach here at a later time.
ESP_LOGE(TAG, "status error %d", mp3_dec_err);
return DECODE_STATUS_NO_DATA_CONTINUE;
}
}
} else {
// if we are dropping data there were no frames decoded
pData->frame_count = 0;
// drop an even count of words
size_t words_to_drop = unread_bytes / BYTES_IN_WORD;
size_t bytes_to_drop = words_to_drop * BYTES_IN_WORD;
// if the unread bytes is less than BYTES_IN_WORD, we should drop any unread bytes
// to avoid the situation where the file could have a few extra bytes at the end
// of the file that isn't at least BYTES_IN_WORD and decoding would get stuck
if(unread_bytes < BYTES_IN_WORD) {
bytes_to_drop = unread_bytes;
}
// shift the read_ptr to drop the bytes in the buffer
pInstance->read_ptr += bytes_to_drop;
/* Sync word not found in frame. Drop data that was read until a word boundary */
ESP_LOGE(TAG, "MP3 sync word not found, dropping %d bytes", bytes_to_drop);
}
return DECODE_STATUS_CONTINUE;
}

View file

@ -0,0 +1,48 @@
#pragma once
#include <stdio.h>
#include "audio_decode_types.h"
#include "mp3dec.h"
typedef struct {
char header[3]; /*!< Always "TAG" */
char title[30]; /*!< Audio title */
char artist[30]; /*!< Audio artist */
char album[30]; /*!< Album name */
char year[4]; /*!< Char array of year */
char comment[30]; /*!< Extra comment */
char genre; /*!< See "https://en.wikipedia.org/wiki/ID3" */
} __attribute__((packed)) mp3_id3_header_v1_t;
typedef struct {
char header[3]; /*!< Always "ID3" */
char ver; /*!< Version, equals to3 if ID3V2.3 */
char revision; /*!< Revision, should be 0 */
char flag; /*!< Flag byte, use Bit[7..5] only */
char size[4]; /*!< TAG size */
} __attribute__((packed)) mp3_id3_header_v2_t;
typedef struct {
// Constants below
uint8_t *data_buf;
/** number of bytes in data_buf */
size_t data_buf_size;
// Values that change at runtime are below
/**
* Total bytes in data_buf,
* not the number of bytes remaining after the read_ptr
*/
size_t bytes_in_data_buf;
/** Pointer to read location in data_buf */
uint8_t *read_ptr;
// set to true if the end of file has been reached
bool eof_reached;
} mp3_instance;
bool is_mp3(FILE *fp);
DECODE_STATUS decode_mp3(HMP3Decoder mp3_decoder, FILE *fp, decode_data *pData, mp3_instance *pInstance);

View file

@ -0,0 +1,608 @@
/**
* @file
* @version 0.1
*
* @copyright Copyright 2021 Espressif Systems (Shanghai) Co. Ltd.
* @copyright Copyright 2022 Chris Morgan <chmorgan@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "sdkconfig.h"
#include "audio_player.h"
#include "audio_wav.h"
#include "audio_mp3.h"
static const char *TAG = "audio";
typedef enum {
AUDIO_PLAYER_REQUEST_NONE = 0,
AUDIO_PLAYER_REQUEST_PAUSE, /**< pause playback */
AUDIO_PLAYER_REQUEST_RESUME, /**< resumed paused playback */
AUDIO_PLAYER_REQUEST_PLAY, /**< initiate playing a new file */
AUDIO_PLAYER_REQUEST_STOP, /**< stop playback */
AUDIO_PLAYER_REQUEST_SHUTDOWN_THREAD, /**< shutdown audio playback thread */
AUDIO_PLAYER_REQUEST_MAX
} audio_player_event_type_t;
typedef struct {
audio_player_event_type_t type;
// valid if type == AUDIO_PLAYER_EVENT_TYPE_PLAY
FILE* fp;
} audio_player_event_t;
typedef enum {
FILE_TYPE_UNKNOWN,
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
FILE_TYPE_MP3,
#endif
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_WAV)
FILE_TYPE_WAV
#endif
} FILE_TYPE;
typedef struct audio_instance {
/**
* Set to true before task is created, false immediately before the
* task is deleted.
*/
bool running;
decode_data output;
QueueHandle_t event_queue;
/* **************** AUDIO CALLBACK **************** */
audio_player_cb_t s_audio_cb;
void *audio_cb_usrt_ctx;
audio_player_state_t state;
audio_player_config_t config;
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_WAV)
wav_instance wav_data;
#endif
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
HMP3Decoder mp3_decoder;
mp3_instance mp3_data;
#endif
} audio_instance_t;
static audio_instance_t instance;
audio_player_state_t audio_player_get_state() {
return instance.state;
}
esp_err_t audio_player_callback_register(audio_player_cb_t call_back, void *user_ctx)
{
#if CONFIG_IDF_TARGET_ARCH_XTENSA
ESP_RETURN_ON_FALSE(esp_ptr_executable(reinterpret_cast<void*>(call_back)), ESP_ERR_INVALID_ARG,
TAG, "Not a valid call back");
#else
ESP_RETURN_ON_FALSE(reinterpret_cast<void*>(call_back), ESP_ERR_INVALID_ARG,
TAG, "Not a valid call back");
#endif
instance.s_audio_cb = call_back;
instance.audio_cb_usrt_ctx = user_ctx;
return ESP_OK;
}
// This function is used in some optional logging functions so we don't want to
// have a cppcheck warning here
// cppcheck-suppress unusedFunction
const char* event_to_string(audio_player_callback_event_t event) {
switch(event) {
case AUDIO_PLAYER_CALLBACK_EVENT_IDLE:
return "AUDIO_PLAYER_CALLBACK_EVENT_IDLE";
case AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT:
return "AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT";
case AUDIO_PLAYER_CALLBACK_EVENT_PLAYING:
return "AUDIO_PLAYER_CALLBACK_EVENT_PLAYING";
case AUDIO_PLAYER_CALLBACK_EVENT_PAUSE:
return "AUDIO_PLAYER_CALLBACK_EVENT_PAUSE";
case AUDIO_PLAYER_CALLBACK_EVENT_SHUTDOWN:
return "AUDIO_PLAYER_CALLBACK_EVENT_SHUTDOWN";
case AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN_FILE_TYPE:
return "AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN_FILE_TYPE";
case AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN:
return "AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN";
}
return "unknown event";
}
static audio_player_callback_event_t state_to_event(audio_player_state_t state) {
audio_player_callback_event_t event = AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN;
switch(state) {
case AUDIO_PLAYER_STATE_IDLE:
event = AUDIO_PLAYER_CALLBACK_EVENT_IDLE;
break;
case AUDIO_PLAYER_STATE_PAUSE:
event = AUDIO_PLAYER_CALLBACK_EVENT_PAUSE;
break;
case AUDIO_PLAYER_STATE_PLAYING:
event = AUDIO_PLAYER_CALLBACK_EVENT_PLAYING;
break;
case AUDIO_PLAYER_STATE_SHUTDOWN:
event = AUDIO_PLAYER_CALLBACK_EVENT_SHUTDOWN;
break;
};
return event;
}
static void dispatch_callback(audio_instance_t *i, audio_player_callback_event_t event) {
LOGI_1("event '%s'", event_to_string(event));
#if CONFIG_IDF_TARGET_ARCH_XTENSA
if (esp_ptr_executable(reinterpret_cast<void*>(i->s_audio_cb))) {
#else
if (reinterpret_cast<void*>(i->s_audio_cb)) {
#endif
audio_player_cb_ctx_t ctx = {
.audio_event = event,
.user_ctx = i->audio_cb_usrt_ctx,
};
i->s_audio_cb(&ctx);
}
}
static void set_state(audio_instance_t *i, audio_player_state_t new_state) {
if(i->state != new_state) {
i->state = new_state;
audio_player_callback_event_t event = state_to_event(new_state);
dispatch_callback(i, event);
}
}
static void audio_instance_init(audio_instance_t &i) {
i.event_queue = NULL;
i.s_audio_cb = NULL;
i.audio_cb_usrt_ctx = NULL;
i.state = AUDIO_PLAYER_STATE_IDLE;
}
static esp_err_t mono_to_stereo(uint32_t output_bits_per_sample, decode_data &adata)
{
size_t data = adata.frame_count * (output_bits_per_sample / BITS_PER_BYTE);
data *= 2;
// do we have enough space in the output buffer to convert mono to stereo?
if(data > adata.samples_capacity_max) {
ESP_LOGE(TAG, "insufficient space in output.samples to convert mono to stereo, need %d, have %d", data, adata.samples_capacity_max);
return ESP_ERR_NO_MEM;
}
size_t new_sample_count = adata.frame_count * 2;
// convert from back to front to allow conversion in-place
//
// NOTE: -1 is because we want to shift to the sample at position X
// but if we do (ptr + X) we end up at the sample at index X instead
// which is one further
int16_t *out = reinterpret_cast<int16_t*>(adata.samples) + (new_sample_count - 1);
int16_t *in = reinterpret_cast<int16_t*>(adata.samples) + (adata.frame_count - 1);
size_t samples = adata.frame_count;
while(samples) {
// write right channel
*out = *in;
out--;
// write left channel
*out = *in;
out--;
// move input buffer back and decrement samples
in--;
samples--;
}
// adjust channels to 2
adata.fmt.channels = 2;
return ESP_OK;
}
static esp_err_t aplay_file(audio_instance_t *i, FILE *fp)
{
LOGI_1("start to decode");
format i2s_format;
memset(&i2s_format, 0, sizeof(i2s_format));
esp_err_t ret = ESP_OK;
audio_player_event_t audio_event = { .type = AUDIO_PLAYER_REQUEST_NONE, .fp = NULL };
FILE_TYPE file_type = FILE_TYPE_UNKNOWN;
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
if(is_mp3(fp)) {
file_type = FILE_TYPE_MP3;
LOGI_1("file is mp3");
// initialize mp3_instance
i->mp3_data.bytes_in_data_buf = 0;
i->mp3_data.read_ptr = i->mp3_data.data_buf;
i->mp3_data.eof_reached = false;
}
#endif
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_WAV)
// This can be a pointless condition depending on the build options, no reason to warn about it
// cppcheck-suppress knownConditionTrueFalse
if(file_type == FILE_TYPE_UNKNOWN)
{
if(is_wav(fp, &i->wav_data)) {
file_type = FILE_TYPE_WAV;
LOGI_1("file is wav");
}
}
#endif
// cppcheck-suppress knownConditionTrueFalse
if(file_type == FILE_TYPE_UNKNOWN) {
ESP_LOGE(TAG, "unknown file type, cleaning up");
dispatch_callback(i, AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN_FILE_TYPE);
goto clean_up;
}
do {
/* Process audio event sent from other task */
if (pdPASS == xQueuePeek(i->event_queue, &audio_event, 0)) {
LOGI_2("event in queue");
if (AUDIO_PLAYER_REQUEST_PAUSE == audio_event.type) {
// receive the pause event to take it off of the queue
xQueueReceive(i->event_queue, &audio_event, 0);
set_state(i, AUDIO_PLAYER_STATE_PAUSE);
// wait until an event is received that will cause playback to resume,
// stop, or change file
while(1) {
xQueuePeek(i->event_queue, &audio_event, portMAX_DELAY);
if((AUDIO_PLAYER_REQUEST_PLAY != audio_event.type) &&
(AUDIO_PLAYER_REQUEST_STOP != audio_event.type) &&
(AUDIO_PLAYER_REQUEST_RESUME != audio_event.type))
{
// receive to discard the event
xQueueReceive(i->event_queue, &audio_event, 0);
} else {
break;
}
}
if(AUDIO_PLAYER_REQUEST_RESUME == audio_event.type) {
// receive to discard the event
xQueueReceive(i->event_queue, &audio_event, 0);
continue;
}
// else fall out of this condition and let the below logic
// handle the other event types
}
if ((AUDIO_PLAYER_REQUEST_STOP == audio_event.type) ||
(AUDIO_PLAYER_REQUEST_PLAY == audio_event.type)) {
ret = ESP_OK;
goto clean_up;
} else {
// receive to discard the event, this event has no
// impact on the state of playback
xQueueReceive(i->event_queue, &audio_event, 0);
continue;
}
}
set_state(i, AUDIO_PLAYER_STATE_PLAYING);
DECODE_STATUS decode_status = DECODE_STATUS_ERROR;
switch(file_type) {
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
case FILE_TYPE_MP3:
decode_status = decode_mp3(i->mp3_decoder, fp, &i->output, &i->mp3_data);
break;
#endif
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_WAV)
case FILE_TYPE_WAV:
decode_status = decode_wav(fp, &i->output, &i->wav_data);
break;
#endif
case FILE_TYPE_UNKNOWN:
ESP_LOGE(TAG, "unexpected unknown file type when decoding");
break;
}
// break out and exit if we aren't supposed to continue decoding
if(decode_status == DECODE_STATUS_CONTINUE)
{
// if mono, convert to stereo as es8311 requires stereo input
// even though it is mono output
if(i->output.fmt.channels == 1) {
LOGI_3("c == 1, mono -> stereo");
ret = mono_to_stereo(i->output.fmt.bits_per_sample, i->output);
if(ret != ESP_OK) {
goto clean_up;
}
}
/* Configure I2S clock if the output format changed */
if ((i2s_format.sample_rate != i->output.fmt.sample_rate) ||
(i2s_format.channels != i->output.fmt.channels) ||
(i2s_format.bits_per_sample != i->output.fmt.bits_per_sample)) {
i2s_format = i->output.fmt;
LOGI_1("format change: sr=%d, bit=%d, ch=%d",
i2s_format.sample_rate,
i2s_format.bits_per_sample,
i2s_format.channels);
i2s_slot_mode_t channel_setting = (i2s_format.channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
ret = i->config.clk_set_fn(i2s_format.sample_rate,
i2s_format.bits_per_sample,
channel_setting);
ESP_GOTO_ON_ERROR(ret, clean_up, TAG, "i2s_set_clk");
}
/**
* Block until all data has been accepted into the i2s driver, however
* the i2s driver has been configured with a buffer to allow for the next round of
* audio decoding to occur while the previous set of samples is finishing playback, in order
* to ensure playback without interruption.
*/
size_t i2s_bytes_written = 0;
size_t bytes_to_write = i->output.frame_count * i->output.fmt.channels * (i2s_format.bits_per_sample / 8);
LOGI_2("c %d, bps %d, bytes %d, frame_count %d",
i->output.fmt.channels,
i2s_format.bits_per_sample,
bytes_to_write,
i->output.frame_count);
i->config.write_fn(i->output.samples, bytes_to_write, &i2s_bytes_written, portMAX_DELAY);
if(bytes_to_write != i2s_bytes_written) {
ESP_LOGE(TAG, "to write %d != written %d", bytes_to_write, i2s_bytes_written);
}
} else if(decode_status == DECODE_STATUS_NO_DATA_CONTINUE)
{
LOGI_2("no data");
} else { // DECODE_STATUS_DONE || DECODE_STATUS_ERROR
LOGI_1("breaking out of playback");
break;
}
} while (true);
clean_up:
return ret;
}
static void audio_task(void *pvParam)
{
audio_instance_t *i = static_cast<audio_instance_t*>(pvParam);
audio_player_event_t audio_event;
while (true) {
// pull items off of the queue until we run into a PLAY request
while(true) {
// zero delay in the case where we are playing as we want to
// send an event indicating either
// PLAYING -> IDLE (IDLE) or PLAYING -> PLAYING (COMPLETED PLAYING NEXT)
// and thus don't want to block until the next request comes in
// in the case when there are no further requests pending
int delay = (i->state == AUDIO_PLAYER_STATE_PLAYING) ? 0 : portMAX_DELAY;
int retval = xQueuePeek(i->event_queue, &audio_event, delay);
if (pdPASS == retval) { // item on the queue, process it
xQueueReceive(i->event_queue, &audio_event, 0);
// if the item is a play request, process it
if(AUDIO_PLAYER_REQUEST_PLAY == audio_event.type) {
if(i->state == AUDIO_PLAYER_STATE_PLAYING) {
dispatch_callback(i, AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT);
} else {
set_state(i, AUDIO_PLAYER_STATE_PLAYING);
}
break;
} else if(AUDIO_PLAYER_REQUEST_SHUTDOWN_THREAD == audio_event.type) {
set_state(i, AUDIO_PLAYER_STATE_SHUTDOWN);
i->running = false;
// should never return
vTaskDelete(NULL);
break;
} else {
// ignore other events when not playing
}
} else { // no items on the queue
// if we are playing transition to idle and indicate the transition via callback
if(i->state == AUDIO_PLAYER_STATE_PLAYING) {
set_state(i, AUDIO_PLAYER_STATE_IDLE);
}
}
}
i->config.mute_fn(AUDIO_PLAYER_UNMUTE);
esp_err_t ret_val = aplay_file(i, audio_event.fp);
if(ret_val != ESP_OK)
{
ESP_LOGE(TAG, "aplay_file() %d", ret_val);
}
i->config.mute_fn(AUDIO_PLAYER_MUTE);
if(audio_event.fp) fclose(audio_event.fp);
}
}
/* **************** AUDIO PLAY CONTROL **************** */
static esp_err_t audio_send_event(audio_instance_t *i, audio_player_event_t event) {
ESP_RETURN_ON_FALSE(NULL != i->event_queue, ESP_ERR_INVALID_STATE,
TAG, "Audio task not started yet");
BaseType_t ret_val = xQueueSend(i->event_queue, &event, 0);
ESP_RETURN_ON_FALSE(pdPASS == ret_val, ESP_ERR_INVALID_STATE,
TAG, "The last event has not been processed yet");
return ESP_OK;
}
esp_err_t audio_player_play(FILE *fp)
{
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_PLAY, .fp = fp };
return audio_send_event(&instance, event);
}
esp_err_t audio_player_pause(void)
{
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_PAUSE, .fp = NULL };
return audio_send_event(&instance, event);
}
esp_err_t audio_player_resume(void)
{
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_RESUME, .fp = NULL };
return audio_send_event(&instance, event);
}
esp_err_t audio_player_stop(void)
{
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_STOP, .fp = NULL };
return audio_send_event(&instance, event);
}
/**
* Can only shut down the playback thread if the thread is not presently playing audio.
* Call audio_player_stop()
*/
static esp_err_t _internal_audio_player_shutdown_thread(void)
{
LOGI_1("%s", __FUNCTION__);
audio_player_event_t event = { .type = AUDIO_PLAYER_REQUEST_SHUTDOWN_THREAD, .fp = NULL };
return audio_send_event(&instance, event);
}
static void cleanup_memory(audio_instance_t &i)
{
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
if(i.mp3_decoder) MP3FreeDecoder(i.mp3_decoder);
if(i.mp3_data.data_buf) free(i.mp3_data.data_buf);
#endif
if(i.output.samples) free(i.output.samples);
vQueueDelete(i.event_queue);
}
esp_err_t audio_player_new(audio_player_config_t config)
{
BaseType_t task_val;
audio_instance_init(instance);
instance.config = config;
/* Audio control event queue */
instance.event_queue = xQueueCreate(4, sizeof(audio_player_event_t));
ESP_RETURN_ON_FALSE(NULL != instance.event_queue, -1, TAG, "xQueueCreate");
/** See https://github.com/ultraembedded/libhelix-mp3/blob/0a0e0673f82bc6804e5a3ddb15fb6efdcde747cd/testwrap/main.c#L74 */
instance.output.samples_capacity = MAX_NCHAN * MAX_NGRAN * MAX_NSAMP;
instance.output.samples_capacity_max = instance.output.samples_capacity * 2;
instance.output.samples = static_cast<uint8_t*>(malloc(instance.output.samples_capacity_max));
LOGI_1("samples_capacity %d bytes", instance.output.samples_capacity_max);
int ret = ESP_OK;
ESP_GOTO_ON_FALSE(NULL != instance.output.samples, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed allocate output buffer");
#if defined(CONFIG_AUDIO_PLAYER_ENABLE_MP3)
instance.mp3_data.data_buf_size = MAINBUF_SIZE * 3;
instance.mp3_data.data_buf = static_cast<uint8_t*>(malloc(instance.mp3_data.data_buf_size));
ESP_GOTO_ON_FALSE(NULL != instance.mp3_data.data_buf, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed allocate mp3 data buffer");
instance.mp3_decoder = MP3InitDecoder();
ESP_GOTO_ON_FALSE(NULL != instance.mp3_decoder, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed create MP3 decoder");
#endif
instance.running = true;
task_val = xTaskCreatePinnedToCore(
(TaskFunction_t) audio_task,
"Audio Task",
4 * 1024,
&instance,
(UBaseType_t) instance.config.priority,
(TaskHandle_t * const) NULL,
(BaseType_t) instance.config.coreID);
ESP_GOTO_ON_FALSE(pdPASS == task_val, ESP_ERR_NO_MEM, cleanup,
TAG, "Failed create audio task");
// start muted
instance.config.mute_fn(AUDIO_PLAYER_MUTE);
return ret;
// At the moment when we run cppcheck there is a lack of esp-idf header files this
// means cppcheck doesn't know that ESP_GOTO_ON_FALSE() etc are making use of this label
// cppcheck-suppress unusedLabelConfiguration
cleanup:
cleanup_memory(instance);
return ret;
}
esp_err_t audio_player_delete() {
const int MAX_RETRIES = 5;
int retries = MAX_RETRIES;
while(instance.running && retries) {
// stop any playback and shutdown the thread
audio_player_stop();
_internal_audio_player_shutdown_thread();
vTaskDelay(pdMS_TO_TICKS(100));
retries--;
}
cleanup_memory(instance);
// if we ran out of retries, return fail code
if(retries == 0) {
return ESP_FAIL;
}
return ESP_OK;
}

View file

@ -0,0 +1,81 @@
#include <string.h>
#include <stdio.h>
#include "audio_wav.h"
static const char *TAG = "wav";
/**
* @param fp
* @param pInstance - Values can be considered valid if true is returned
* @return true if file is a wav file
*/
bool is_wav(FILE *fp, wav_instance *pInstance) {
fseek(fp, 0, SEEK_SET);
size_t bytes_read = fread(&pInstance->header, 1, sizeof(wav_header_t), fp);
if(bytes_read != sizeof(wav_header_t)) {
return false;
}
wav_header_t *wav_head = &pInstance->header;
if((NULL == strstr(reinterpret_cast<char *>(wav_head->ChunkID), "RIFF")) ||
(NULL == strstr(reinterpret_cast<char*>(wav_head->Format), "WAVE"))
)
{
return false;
}
// decode chunks until we find the 'data' one
wav_subchunk_header_t subchunk;
while(true) {
bytes_read = fread(&subchunk, 1, sizeof(wav_subchunk_header_t), fp);
if(bytes_read != sizeof(wav_subchunk_header_t)) {
return false;
}
if(memcmp(subchunk.SubchunkID, "data", 4) == 0)
{
break;
} else {
// advance beyond this subchunk, it could be a 'LIST' chunk with file info or some other unhandled subchunk
fseek(fp, subchunk.SubchunkSize, SEEK_CUR);
}
}
LOGI_2("sample_rate=%d, channels=%d, bps=%d",
wav_head->SampleRate,
wav_head->NumChannels,
wav_head->BitsPerSample);
return true;
}
/**
* @return true if data remains, false on error or end of file
*/
DECODE_STATUS decode_wav(FILE *fp, decode_data *pData, wav_instance *pInstance) {
// read an even multiple of frames that can fit into output_samples buffer, otherwise
// we would have to manage what happens with partial frames in the output buffer
size_t bytes_per_frame = (pInstance->header.BitsPerSample / BITS_PER_BYTE) * pInstance->header.NumChannels;
size_t frames_to_read = pData->samples_capacity / bytes_per_frame;
size_t bytes_to_read = frames_to_read * bytes_per_frame;
size_t bytes_read = fread(pData->samples, 1, bytes_to_read, fp);
pData->fmt.channels = pInstance->header.NumChannels;
pData->fmt.bits_per_sample = pInstance->header.BitsPerSample;
pData->fmt.sample_rate = pInstance->header.SampleRate;
if(bytes_read != 0)
{
pData->frame_count = (bytes_read / (pInstance->header.BitsPerSample / BITS_PER_BYTE)) / pInstance->header.NumChannels;
} else {
pData->frame_count = 0;
}
LOGI_2("bytes_per_frame %d, bytes_to_read %d, bytes_read %d, frame_count %d",
bytes_per_frame, bytes_to_read, bytes_read,
pData->frame_count);
return (bytes_read == 0) ? DECODE_STATUS_DONE : DECODE_STATUS_CONTINUE;
}

View file

@ -0,0 +1,34 @@
#pragma once
#include <stdio.h>
#include "audio_log.h"
#include "audio_decode_types.h"
typedef struct {
// The "RIFF" chunk descriptor
uint8_t ChunkID[4];
int32_t ChunkSize;
uint8_t Format[4];
// The "fmt" sub-chunk
uint8_t Subchunk1ID[4];
int32_t Subchunk1Size;
int16_t AudioFormat;
int16_t NumChannels;
int32_t SampleRate;
int32_t ByteRate;
int16_t BlockAlign;
int16_t BitsPerSample;
} wav_header_t;
typedef struct {
// The "data" sub-chunk
uint8_t SubchunkID[4];
int32_t SubchunkSize;
} wav_subchunk_header_t;
typedef struct {
wav_header_t header;
} wav_instance;
bool is_wav(FILE *fp, wav_instance *pInstance);
DECODE_STATUS decode_wav(FILE *fp, decode_data *pData, wav_instance *pInstance);

View file

@ -0,0 +1,8 @@
dependencies:
chmorgan/esp-libhelix-mp3:
version: '>=1.0.0,<2.0.0'
idf:
version: '>=5.0'
description: Lightweight audio decoding component for esp processors
url: https://github.com/chmorgan/esp-audio-player
version: 1.0.7

View file

@ -0,0 +1,182 @@
/**
* @file
* @version 0.1
*
* @copyright Copyright 2021 Espressif Systems (Shanghai) Co. Ltd.
* @copyright Copyright 2022 Chris Morgan <chmorgan@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Design notes
*
* - There is a distinct event for playing -> playing state transitions.
* COMPLETED_PLAYING_NEXT is helpful for users of the audio player to know
* the difference between playing and transitioning to another audio file
* vs. detecting that the audio file transitioned by looking at
* events indicating IDLE and then PLAYING within a short period of time.
*
* State machine diagram
*
* cb is the callback function registered with audio_player_callback_register()
*
* cb(PLAYING) cb(PLAYING)
* _______________________________ ____________________________________
* | | | |
* | | | |
* | cb(IDLE) V V cb(PAUSE) |
* Idle <------------------------ Playing ----------------------------> Pause
* ^ |_____^ |
* | cb(COMPLETED_PLAYING_NEXT) |
* | |
* |______________________________________________________________________|
* cb(IDLE)
*
*/
#pragma once
#include <stddef.h>
#include <stdio.h>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "driver/i2s_std.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
AUDIO_PLAYER_STATE_IDLE,
AUDIO_PLAYER_STATE_PLAYING,
AUDIO_PLAYER_STATE_PAUSE,
AUDIO_PLAYER_STATE_SHUTDOWN
} audio_player_state_t;
/**
* @brief Get the audio player state
*
* @return the present audio_player_state_t
*/
audio_player_state_t audio_player_get_state();
typedef enum {
AUDIO_PLAYER_CALLBACK_EVENT_IDLE, /**< Player is idle, not playing audio */
AUDIO_PLAYER_CALLBACK_EVENT_COMPLETED_PLAYING_NEXT, /**< Player is playing and playing a new audio file */
AUDIO_PLAYER_CALLBACK_EVENT_PLAYING, /**< Player is playing */
AUDIO_PLAYER_CALLBACK_EVENT_PAUSE, /**< Player is pausing */
AUDIO_PLAYER_CALLBACK_EVENT_SHUTDOWN, /**< Player is shutting down */
AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN_FILE_TYPE, /**< File type is unknown */
AUDIO_PLAYER_CALLBACK_EVENT_UNKNOWN /**< Unknown event */
} audio_player_callback_event_t;
typedef struct {
audio_player_callback_event_t audio_event;
void *user_ctx;
} audio_player_cb_ctx_t;
/** Audio callback function type */
typedef void (*audio_player_cb_t)(audio_player_cb_ctx_t *);
/**
* @brief Play mp3 audio file.
*
* Will interrupt a present playback and start the new playback
* as soon as possible.
*
* @param fp - If ESP_OK is returned, will be fclose()ed by the audio system
* when the playback has completed or in the event of a playback error.
* If not ESP_OK returned then should be fclose()d by the caller.
* @return
* - ESP_OK: Success in queuing play request
* - Others: Fail
*/
esp_err_t audio_player_play(FILE *fp);
/**
* @brief Pause playback
*
* @return
* - ESP_OK: Success in queuing pause request
* - Others: Fail
*/
esp_err_t audio_player_pause(void);
/**
* @brief Resume playback
*
* Has no effect if playback is not in progress
* @return esp_err_t
* - ESP_OK: Success in queuing resume request
* - Others: Fail
*/
esp_err_t audio_player_resume(void);
/**
* @brief Stop playback
*
* Has no effect if playback is already stopped
* @return esp_err_t
* - ESP_OK: Success in queuing resume request
* - Others: Fail
*/
esp_err_t audio_player_stop(void);
/**
* @brief Register callback for audio event
*
* @param call_back Call back function
* @param user_ctx User context
* @return
* - ESP_OK: Success
* - Others: Fail
*/
esp_err_t audio_player_callback_register(audio_player_cb_t call_back, void *user_ctx);
typedef enum {
AUDIO_PLAYER_MUTE,
AUDIO_PLAYER_UNMUTE
} AUDIO_PLAYER_MUTE_SETTING;
typedef esp_err_t (*audio_player_mute_fn)(AUDIO_PLAYER_MUTE_SETTING setting);
typedef esp_err_t (*audio_reconfig_std_clock)(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch);
typedef esp_err_t (*audio_player_write_fn)(void *audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms);
typedef struct {
audio_player_mute_fn mute_fn;
audio_reconfig_std_clock clk_set_fn;
audio_player_write_fn write_fn;
UBaseType_t priority; /*< FreeRTOS task priority */
BaseType_t coreID; /*< ESP32 core ID */
} audio_player_config_t;
/**
* @brief Initialize hardware, allocate memory, create and start audio task.
* Call before any other 'audio' functions.
*
* @param port - The i2s port for output
* @return esp_err_t
*/
esp_err_t audio_player_new(audio_player_config_t config);
/**
* @brief Shut down audio task, free allocated memory.
*
* @return esp_err_t ESP_OK upon success, ESP_FAIL if unable to shutdown due to retries exhausted
*/
esp_err_t audio_player_delete();
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils audio_player
EMBED_TXTFILES gs-16b-1c-44100hz.mp3)

View file

@ -0,0 +1,282 @@
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_log.h"
#include "esp_check.h"
#include "unity.h"
#include "audio_player.h"
#include "driver/gpio.h"
#include "test_utils.h"
#include "freertos/semphr.h"
static const char *TAG = "AUDIO PLAYER TEST";
#define CONFIG_BSP_I2S_NUM 1
/* Audio */
#define BSP_I2S_SCLK (GPIO_NUM_17)
#define BSP_I2S_MCLK (GPIO_NUM_2)
#define BSP_I2S_LCLK (GPIO_NUM_47)
#define BSP_I2S_DOUT (GPIO_NUM_15) // To Codec ES8311
#define BSP_I2S_DSIN (GPIO_NUM_16) // From ADC ES7210
#define BSP_POWER_AMP_IO (GPIO_NUM_46)
#define BSP_MUTE_STATUS (GPIO_NUM_1)
/**
* @brief ESP-BOX I2S pinout
*
* Can be used for i2s_std_gpio_config_t and/or i2s_std_config_t initialization
*/
#define BSP_I2S_GPIO_CFG \
{ \
.mclk = BSP_I2S_MCLK, \
.bclk = BSP_I2S_SCLK, \
.ws = BSP_I2S_LCLK, \
.dout = BSP_I2S_DOUT, \
.din = BSP_I2S_DSIN, \
.invert_flags = { \
.mclk_inv = false, \
.bclk_inv = false, \
.ws_inv = false, \
}, \
}
/**
* @brief Mono Duplex I2S configuration structure
*
* This configuration is used by default in bsp_audio_init()
*/
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate) \
{ \
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
.gpio_cfg = BSP_I2S_GPIO_CFG, \
}
static i2s_chan_handle_t i2s_tx_chan;
static i2s_chan_handle_t i2s_rx_chan;
static esp_err_t bsp_i2s_write(void * audio_buffer, size_t len, size_t *bytes_written, uint32_t timeout_ms)
{
esp_err_t ret = ESP_OK;
ret = i2s_channel_write(i2s_tx_chan, (char *)audio_buffer, len, bytes_written, timeout_ms);
return ret;
}
static esp_err_t bsp_i2s_reconfig_clk(uint32_t rate, uint32_t bits_cfg, i2s_slot_mode_t ch)
{
esp_err_t ret = ESP_OK;
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate),
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bits_cfg, (i2s_slot_mode_t)ch),
.gpio_cfg = BSP_I2S_GPIO_CFG,
};
ret |= i2s_channel_disable(i2s_tx_chan);
ret |= i2s_channel_reconfig_std_clock(i2s_tx_chan, &std_cfg.clk_cfg);
ret |= i2s_channel_reconfig_std_slot(i2s_tx_chan, &std_cfg.slot_cfg);
ret |= i2s_channel_enable(i2s_tx_chan);
return ret;
}
static esp_err_t audio_mute_function(AUDIO_PLAYER_MUTE_SETTING setting) {
ESP_LOGI(TAG, "mute setting %d", setting);
return ESP_OK;
}
TEST_CASE("audio player can be newed and deleted", "[audio player]")
{
audio_player_config_t config = { .mute_fn = audio_mute_function,
.write_fn = bsp_i2s_write,
.clk_set_fn = bsp_i2s_reconfig_clk,
.priority = 0,
.coreID = 0 };
esp_err_t ret = audio_player_new(config);
TEST_ASSERT_EQUAL(ret, ESP_OK);
ret = audio_player_delete();
TEST_ASSERT_EQUAL(ret, ESP_OK);
audio_player_state_t state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_SHUTDOWN);
}
static esp_err_t bsp_audio_init(const i2s_std_config_t *i2s_config, i2s_chan_handle_t *tx_channel, i2s_chan_handle_t *rx_channel)
{
/* Setup I2S peripheral */
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(CONFIG_BSP_I2S_NUM, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, tx_channel, rx_channel));
/* Setup I2S channels */
const i2s_std_config_t std_cfg_default = BSP_I2S_DUPLEX_MONO_CFG(22050);
const i2s_std_config_t *p_i2s_cfg = &std_cfg_default;
if (i2s_config != NULL) {
p_i2s_cfg = i2s_config;
}
if (tx_channel != NULL) {
ESP_ERROR_CHECK(i2s_channel_init_std_mode(*tx_channel, p_i2s_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(*tx_channel));
}
if (rx_channel != NULL) {
ESP_ERROR_CHECK(i2s_channel_init_std_mode(*rx_channel, p_i2s_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(*rx_channel));
}
/* Setup power amplifier pin */
const gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = BIT64(BSP_POWER_AMP_IO),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLDOWN_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
return ESP_OK;
}
static audio_player_callback_event_t expected_event;
static QueueHandle_t event_queue;
static void audio_player_callback(audio_player_cb_ctx_t *ctx)
{
TEST_ASSERT_EQUAL(ctx->audio_event, expected_event);
// wake up the test so it can continue to the next step
TEST_ASSERT_EQUAL(xQueueSend(event_queue, &(ctx->audio_event), 0), pdPASS);
}
TEST_CASE("audio player states and callbacks are correct", "[audio player]")
{
audio_player_callback_event_t event;
/* Configure I2S peripheral and Power Amplifier */
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100),
.slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = BSP_I2S_GPIO_CFG,
};
esp_err_t ret = bsp_audio_init(&std_cfg, &i2s_tx_chan, &i2s_rx_chan);
TEST_ASSERT_EQUAL(ret, ESP_OK);
audio_player_config_t config = { .mute_fn = audio_mute_function,
.write_fn = bsp_i2s_write,
.clk_set_fn = bsp_i2s_reconfig_clk,
.priority = 0,
.coreID = 0 };
ret = audio_player_new(config);
TEST_ASSERT_EQUAL(ret, ESP_OK);
event_queue = xQueueCreate(1, sizeof(audio_player_callback_event_t));
TEST_ASSERT_NOT_NULL(event_queue);
ret = audio_player_callback_register(audio_player_callback, NULL);
TEST_ASSERT_EQUAL(ret, ESP_OK);
audio_player_state_t state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_IDLE);
extern const char mp3_start[] asm("_binary_gs_16b_1c_44100hz_mp3_start");
extern const char mp3_end[] asm("_binary_gs_16b_1c_44100hz_mp3_end");
// -1 due to the size being 1 byte too large, I think because end is the byte
// immediately after the last byte in the memory but I'm not sure - cmm 2022-08-20
//
// Suppression as these are linker symbols and cppcheck doesn't know how to ensure
// they are the same object
// cppcheck-suppress comparePointers
size_t mp3_size = (mp3_end - mp3_start) - 1;
ESP_LOGI(TAG, "mp3_size %zu bytes", mp3_size);
FILE *fp = fmemopen((void*)mp3_start, mp3_size, "rb");
TEST_ASSERT_NOT_NULL(fp);
///////////////
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_PLAYING;
ret = audio_player_play(fp);
TEST_ASSERT_EQUAL(ret, ESP_OK);
// wait for playing event to arrive
TEST_ASSERT_EQUAL(xQueueReceive(event_queue, &event, pdMS_TO_TICKS(100)), pdPASS);
// confirm state is playing
state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_PLAYING);
///////////////
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_PAUSE;
ret = audio_player_pause();
TEST_ASSERT_EQUAL(ret, ESP_OK);
// wait for paused event to arrive
TEST_ASSERT_EQUAL(xQueueReceive(event_queue, &event, pdMS_TO_TICKS(100)), pdPASS);
state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_PAUSE);
////////////////
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_PLAYING;
ret = audio_player_resume();
TEST_ASSERT_EQUAL(ret, ESP_OK);
// wait for paused event to arrive
TEST_ASSERT_EQUAL(xQueueReceive(event_queue, &event, pdMS_TO_TICKS(100)), pdPASS);
///////////////
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_IDLE;
// the track is 16 seconds long so lets wait a bit here
int sleep_seconds = 16;
ESP_LOGI(TAG, "sleeping for %d seconds for playback to complete", sleep_seconds);
vTaskDelay(pdMS_TO_TICKS(sleep_seconds * 1000));
// wait for idle event to arrive
TEST_ASSERT_EQUAL(xQueueReceive(event_queue, &event, pdMS_TO_TICKS(100)), pdPASS);
state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_IDLE);
///////////////
expected_event = AUDIO_PLAYER_CALLBACK_EVENT_SHUTDOWN;
ret = audio_player_delete();
TEST_ASSERT_EQUAL(ret, ESP_OK);
// wait for idle event to arrive
TEST_ASSERT_EQUAL(xQueueReceive(event_queue, &event, pdMS_TO_TICKS(100)), pdPASS);
state = audio_player_get_state();
TEST_ASSERT_EQUAL(state, AUDIO_PLAYER_STATE_SHUTDOWN);
vQueueDelete(event_queue);
TEST_ESP_OK(i2s_channel_disable(i2s_tx_chan));
TEST_ESP_OK(i2s_channel_disable(i2s_rx_chan));
TEST_ESP_OK(i2s_del_channel(i2s_tx_chan));
TEST_ESP_OK(i2s_del_channel(i2s_rx_chan));
ESP_LOGI(TAG, "NOTE: a memory leak will be reported the first time this test runs.\n");
ESP_LOGI(TAG, "esp-idf v4.4.1 and v4.4.2 both leak memory between i2s_driver_install() and i2s_driver_uninstall()\n");
}

View file

@ -0,0 +1,6 @@
#
#Component Makefile
#
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
COMPONENT_EMBED_TXTFILES += gs-16b-1c-44100hz.mp3

View file

@ -0,0 +1 @@
cbb76089dc2c5749f7b470e2e70aedc44c9da519e04eb9a67d4c7ec275229e53

View file

@ -0,0 +1,3 @@
[submodule "libhelix-mp3"]
path = libhelix-mp3
url = https://github.com/chmorgan/libhelix-mp3.git

View file

@ -0,0 +1,11 @@
idf_component_register(
SRC_DIRS
"libhelix-mp3/."
"libhelix-mp3/real"
INCLUDE_DIRS
"libhelix-mp3/pub"
PRIV_INCLUDE_DIRS
"libhelix-mp3/real")
# Some of warinings, block them.
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-but-set-variable)

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,3 @@
# esp-libhelix-mp3
ESP32 (and others) component for the libhelix-mp3 mp3 decoding library.

View file

@ -0,0 +1,6 @@
dependencies:
idf:
version: '>=4.1.0'
description: libhelix-mp3 (mp3 decoder) component
url: https://github.com/chmorgan/esp-libhelix-mp3
version: 1.0.3

View file

@ -0,0 +1,30 @@
Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
The contents of this directory, and (except where otherwise
indicated) the directories included within this directory, are
subject to the current version of the RealNetworks Public Source
License (the "RPSL") available at RPSL.txt in this directory, unless
you have licensed the directory under the current version of the
RealNetworks Community Source License (the "RCSL") available at
RCSL.txt in this directory, in which case the RCSL will apply. You
may also obtain the license terms directly from RealNetworks. You
may not use the files in this directory except in compliance with the
RPSL or, if you have a valid RCSL with RealNetworks applicable to
this directory, the RCSL. Please see the applicable RPSL or RCSL for
the rights, obligations and limitations governing use of the contents
of the directory.
This directory is part of the Helix DNA Technology. RealNetworks is
the developer of the Original Code and owns the copyrights in the
portions it created.
This directory, and the directories included with this directory, are
distributed and made available on an 'AS IS' basis, WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY
DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
QUIET ENJOYMENT OR NON-INFRINGEMENT.
Technology Compatibility Kit Test Suite(s) Location:
http://www.helixcommunity.org/content/tck

View file

@ -0,0 +1,948 @@
The RCSL is made up of a base agreement and a few Attachments.
For Research and Development use, you agree to the terms of the
RCSL R&D License (base RCSL and Attachments A, B, and C)
For Commercial Use (either distribution or internal commercial
deployment) of the Helix DNA with or without support for RealNetworks'
RealAudio and RealVideo Add-on Technology, you agree to the
terms of the same RCSL R&D license
and execute one or more additional Commercial Use License attachments
<see http://www.helixcommunity.org/content/rcsl-attachments>.
------------------------------------------------------------------------
REALNETWORKS COMMUNITY SOURCE LICENSE
Version 1.2 (Rev. Date: January 22, 2003).
RECITALS
Original Contributor has developed Specifications, Source Code
implementations and Executables of certain Technology; and
Original Contributor desires to license the Technology to a large
community to facilitate research, innovation and product development
while maintaining compatibility of such products with the Technology as
delivered by Original Contributor; and
Original Contributor desires to license certain Trademarks for the
purpose of branding products that are compatible with the relevant
Technology delivered by Original Contributor; and
You desire to license the Technology and possibly certain Trademarks
from Original Contributor on the terms and conditions specified in this
License.
In consideration for the mutual covenants contained herein, You and
Original Contributor agree as follows:
AGREEMENT
*1. Introduction.*
The RealNetworks Community Source License ("RCSL") and effective
attachments ("License") may include five distinct licenses:
i) Research Use license -- License plus Attachments A, B and C only.
ii) Commercial Use and Trademark License, which may be for Internal
Deployment Use or external distribution, or both -- License plus
Attachments A, B, C, and D.
iii) Technology Compatibility Kit (TCK) license -- Attachment C.
iv) Add-On Technology License (Executable) Commercial Use License
-Attachment F.
v) Add-On Technology Source Code Porting and Optimization
License-Attachment G.
The Research Use license is effective when You click and accept this
License. The TCK is effective when You click and accept this License,
unless otherwise specified in the TCK attachments. The Commercial Use
and Trademark, Add-On Technology License, and the Add-On Technology
Source Code Porting and Optimization licenses must each be signed by You
and Original Contributor to become effective. Once effective, these
licenses and the associated requirements and responsibilities are
cumulative. Capitalized terms used in this License are defined in the
Glossary.
*2. License Grants.*
2.1 Original Contributor Grant.
Subject to Your compliance with Sections 3, 8.10 and Attachment A of
this License, Original Contributor grants to You a worldwide,
royalty-free, non-exclusive license, to the extent of Original
Contributor's Intellectual Property Rights covering the Original Code,
Upgraded Code and Specifications, to do the following:
(a) Research Use License:
(i) use, reproduce and modify the Original Code, Upgraded Code and
Specifications to create Modifications and Reformatted Specifications
for Research Use by You;
(ii) publish and display Original Code, Upgraded Code and Specifications
with, or as part of Modifications, as permitted under Section 3.1(b) below;
(iii) reproduce and distribute copies of Original Code and Upgraded Code
to Licensees and students for Research Use by You;
(iv) compile, reproduce and distribute Original Code and Upgraded Code
in Executable form, and Reformatted Specifications to anyone for
Research Use by You.
(b) Other than the licenses expressly granted in this License, Original
Contributor retains all right, title, and interest in Original Code and
Upgraded Code and Specifications.
2.2 Your Grants.
(a) To Other Licensees. You hereby grant to each Licensee a license to
Your Error Corrections and Shared Modifications, of the same scope and
extent as Original Contributor's licenses under Section 2.1 a) above
relative to Research Use and Attachment D relative to Commercial Use.
(b) To Original Contributor. You hereby grant to Original Contributor a
worldwide, royalty-free, non-exclusive, perpetual and irrevocable
license, to the extent of Your Intellectual Property Rights covering
Your Error Corrections, Shared Modifications and Reformatted
Specifications, to use, reproduce, modify, display and distribute Your
Error Corrections, Shared Modifications and Reformatted Specifications,
in any form, including the right to sublicense such rights through
multiple tiers of distribution.
(c) Other than the licenses expressly granted in Sections 2.2(a) and (b)
above, and the restrictions set forth in Section 3.1(d)(iv) below, You
retain all right, title, and interest in Your Error Corrections, Shared
Modifications and Reformatted Specifications.
2.3 Contributor Modifications.
You may use, reproduce, modify, display and distribute Contributor Error
Corrections, Shared Modifications and Reformatted Specifications,
obtained by You under this License, to the same scope and extent as with
Original Code, Upgraded Code and Specifications.
2.4 Subcontracting.
You may deliver the Source Code of Covered Code to other Licensees
having at least a Research Use license, for the sole purpose of
furnishing development services to You in connection with Your rights
granted in this License. All such Licensees must execute appropriate
documents with respect to such work consistent with the terms of this
License, and acknowledging their work-made-for-hire status or assigning
exclusive right to the work product and associated Intellectual Property
Rights to You.
*3. Requirements and Responsibilities*.
3.1 Research Use License.
As a condition of exercising the rights granted under Section 2.1(a)
above, You agree to comply with the following:
(a) Your Contribution to the Community. All Error Corrections and Shared
Modifications which You create or contribute to are automatically
subject to the licenses granted under Section 2.2 above. You are
encouraged to license all of Your other Modifications under Section 2.2
as Shared Modifications, but are not required to do so. You agree to
notify Original Contributor of any errors in the Specification.
(b) Source Code Availability. You agree to provide all Your Error
Corrections to Original Contributor as soon as reasonably practicable
and, in any event, prior to Internal Deployment Use or Commercial Use,
if applicable. Original Contributor may, at its discretion, post Source
Code for Your Error Corrections and Shared Modifications on the
Community Webserver. You may also post Error Corrections and Shared
Modifications on a web-server of Your choice; provided, that You must
take reasonable precautions to ensure that only Licensees have access to
such Error Corrections and Shared Modifications. Such precautions shall
include, without limitation, a password protection scheme limited to
Licensees and a click-on, download certification of Licensee status
required of those attempting to download from the server. An example of
an acceptable certification is attached as Attachment A-2.
(c) Notices. All Error Corrections and Shared Modifications You create
or contribute to must include a file documenting the additions and
changes You made and the date of such additions and changes. You must
also include the notice set forth in Attachment A-1 in the file header.
If it is not possible to put the notice in a particular Source Code file
due to its structure, then You must include the notice in a location
(such as a relevant directory file), where a recipient would be most
likely to look for such a notice.
(d) Redistribution.
(i) Source. Covered Code may be distributed in Source Code form only to
another Licensee (except for students as provided below). You may not
offer or impose any terms on any Covered Code that alter the rights,
requirements, or responsibilities of such Licensee. You may distribute
Covered Code to students for use in connection with their course work
and research projects undertaken at accredited educational institutions.
Such students need not be Licensees, but must be given a copy of the
notice set forth in Attachment A-3 and such notice must also be included
in a file header or prominent location in the Source Code made available
to such students.
(ii) Executable. You may distribute Executable version(s) of Covered
Code to Licensees and other third parties only for the purpose of
evaluation and comment in connection with Research Use by You and under
a license of Your choice, but which limits use of such Executable
version(s) of Covered Code only to that purpose.
(iii) Modified Class, Interface and Package Naming. In connection with
Research Use by You only, You may use Original Contributor's class,
Interface and package names only to accurately reference or invoke the
Source Code files You modify. Original Contributor grants to You a
limited license to the extent necessary for such purposes.
(iv) You expressly agree that any distribution, in whole or in part, of
Modifications developed by You shall only be done pursuant to the terms
and conditions of this License.
(e) Extensions.
(i) Covered Code. You may not include any Source Code of Community Code
in any Extensions. You may include the compiled Header Files of
Community Code in an Extension provided that Your use of the Covered
Code, including Heading Files, complies with the Commercial Use License,
the TCK and all other terms of this License.
(ii) Publication. No later than the date on which You first distribute
such Extension for Commercial Use, You must publish to the industry, on
a non-confidential basis and free of all copyright restrictions with
respect to reproduction and use, an accurate and current specification
for any Extension. In addition, You must make available an appropriate
test suite, pursuant to the same rights as the specification,
sufficiently detailed to allow any third party reasonably skilled in the
technology to produce implementations of the Extension compatible with
the specification. Such test suites must be made available as soon as
reasonably practicable but, in no event, later than ninety (90) days
after Your first Commercial Use of the Extension. You must use
reasonable efforts to promptly clarify and correct the specification and
the test suite upon written request by Original Contributor.
(iii) Open. You agree to refrain from enforcing any Intellectual
Property Rights You may have covering any interface(s) of Your
Extension, which would prevent the implementation of such interface(s)
by Original Contributor or any Licensee. This obligation does not
prevent You from enforcing any Intellectual Property Right You have that
would otherwise be infringed by an implementation of Your Extension.
(iv) Interface Modifications and Naming. You may not modify or add to
the GUID space * * "xxxxxxxx-0901-11d1-8B06-00A024406D59" or any other
GUID space designated by Original Contributor. You may not modify any
Interface prefix provided with the Covered Code or any other prefix
designated by Original Contributor.* *
* *
(f) You agree that any Specifications provided to You by Original
Contributor are confidential and proprietary information of Original
Contributor. You must maintain the confidentiality of the Specifications
and may not disclose them to any third party without Original
Contributor's prior written consent. You may only use the Specifications
under the terms of this License and only for the purpose of implementing
the terms of this License with respect to Covered Code. You agree not
use, copy or distribute any such Specifications except as provided in
writing by Original Contributor.
3.2 Commercial Use License.
You may not make Commercial Use of any Covered Code unless You and
Original Contributor have executed a copy of the Commercial Use and
Trademark License attached as Attachment D.
*4. Versions of the License.*
4.1 License Versions.
Original Contributor may publish revised versions of the License from
time to time. Each version will be given a distinguishing version number.
4.2 Effect.
Once a particular version of Covered Code has been provided under a
version of the License, You may always continue to use such Covered Code
under the terms of that version of the License. You may also choose to
use such Covered Code under the terms of any subsequent version of the
License. No one other than Original Contributor has the right to
promulgate License versions.
4.3 Multiple-Licensed Code.
Original Contributor may designate portions of the Covered Code as
"Multiple-Licensed." "Multiple-Licensed" means that the Original
Contributor permits You to utilize those designated portions of the
Covered Code under Your choice of this License or the alternative
license(s), if any, specified by the Original Contributor in an
Attachment to this License.
*5. Disclaimer of Warranty.*
5.1 COVERED CODE PROVIDED AS IS.
COVERED CODE IS PROVIDED UNDER THIS LICENSE "AS IS," WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT
FOR A PARTICULAR PURPOSE OR NON-INFRINGING. YOU AGREE TO BEAR THE ENTIRE
RISK IN CONNECTION WITH YOUR USE AND DISTRIBUTION OF COVERED CODE UNDER
THIS LICENSE. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART
OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER
EXCEPT SUBJECT TO THIS DISCLAIMER.
5.2 Not Designed for High Risk Activities.
You acknowledge that Original Code, Upgraded Code and Specifications are
not designed or intended for use in high risk activities including, but
not limited to: (i) on-line control of aircraft, air traffic, aircraft
navigation or aircraft communications; or (ii) in the design,
construction, operation or maintenance of any nuclear facility. Original
Contributor disclaims any express or implied warranty of fitness for
such uses.
*6. Termination.*
6.1 By You.
You may terminate this Research Use license at anytime by providing
written notice to Original Contributor.
6.2 By Original Contributor.
This License and the rights granted hereunder will terminate:
(i) automatically if You fail to comply with the terms of this License
and fail to cure such breach within 30 days of receipt of written notice
of the breach;
(ii) immediately in the event of circumstances specified in Sections 7.1
and 8.4; or
(iii) at Original Contributor's discretion upon any action initiated by
You (including by cross-claim or counter claim) alleging that use or
distribution by Original Contributor or any Licensee, of Original Code,
Upgraded Code, Error Corrections, Shared Modifications or Specifications
infringe a patent owned or controlled by You.
6.3 Effective of Termination.
Upon termination, You agree to discontinue use of and destroy all copies
of Covered Code in Your possession. All sublicenses to the Covered Code
which You have properly granted shall survive any termination of this
License. Provisions that, by their nature, should remain in effect
beyond the termination of this License shall survive including, without
limitation, Sections 2.2, 3, 5, 7 and 8.
6.4 No Compensation.
Each party waives and releases the other from any claim to compensation
or indemnity for permitted or lawful termination of the business
relationship established by this License.
*7. Liability.*
7.1 Infringement. Should any of the Original Code, Upgraded Code, TCK or
Specifications ("Materials") become the subject of a claim of
infringement, Original Contributor may, at its sole option, (i) attempt
to procure the rights necessary for You to continue using the Materials,
(ii) modify the Materials so that they are no longer infringing, or
(iii) terminate Your right to use the Materials, immediately upon
written notice, and refund to You the amount, if any, having then
actually been paid by You to Original Contributor for the Original Code,
Upgraded Code and TCK, depreciated on a straight line, five year basis.
7.2 LIMITATION OF LIABILITY. TO THE FULL EXTENT ALLOWED BY APPLICABLE
LAW, ORIGINAL CONTRIBUTOR'S LIABILITY TO YOU FOR CLAIMS RELATING TO THIS
LICENSE, WHETHER FOR BREACH OR IN TORT, SHALL BE LIMITED TO ONE HUNDRED
PERCENT (100%) OF THE AMOUNT HAVING THEN ACTUALLY BEEN PAID BY YOU TO
ORIGINAL CONTRIBUTOR FOR ALL COPIES LICENSED HEREUNDER OF THE PARTICULAR
ITEMS GIVING RISE TO SUCH CLAIM, IF ANY, DURING THE TWELVE MONTHS
PRECEDING THE CLAIMED BREACH. IN NO EVENT WILL YOU (RELATIVE TO YOUR
SHARED MODIFICATIONS OR ERROR CORRECTIONS) OR ORIGINAL CONTRIBUTOR BE
LIABLE FOR ANY INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
DAMAGES IN CONNECTION WITH OR RISING OUT OF THIS LICENSE (INCLUDING,
WITHOUT LIMITATION, LOSS OF PROFITS, USE, DATA, OR OTHER ECONOMIC
ADVANTAGE), HOWEVER IT ARISES AND ON ANY THEORY OF LIABILITY, WHETHER IN
AN ACTION FOR CONTRACT, STRICT LIABILITY OR TORT (INCLUDING NEGLIGENCE)
OR OTHERWISE, WHETHER OR NOT YOU OR ORIGINAL CONTRIBUTOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE AND NOTWITHSTANDING THE
FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY.
*8. Miscellaneous.*
8.1 Trademark.
You shall not use any Trademark unless You and Original Contributor
execute a copy of the Commercial Use and Trademark License Agreement
attached hereto as Attachment D. Except as expressly provided in the
License, You are granted no right, title or license to, or interest in,
any Trademarks. Whether or not You and Original Contributor enter into
the Trademark License, You agree not to (i) challenge Original
Contributor's ownership or use of Trademarks; (ii) attempt to register
any Trademarks, or any mark or logo substantially similar thereto; or
(iii) incorporate any Trademarks into Your own trademarks, product
names, service marks, company names, or domain names.
8.2 Integration.
This License represents the complete agreement concerning the subject
matter hereof.
8.3 Assignment.
Original Contributor may assign this License, and its rights and
obligations hereunder, in its sole discretion. You may assign the
Research Use portions of this License and the TCK license to a third
party upon prior written notice to Original Contributor (which may be
provided electronically via the Community Web-Server). You may not
assign the Commercial Use and Trademark license, the Add-On Technology
License, or the Add-On Technology Source Code Porting License, including
by way of merger (regardless of whether You are the surviving entity) or
acquisition, without Original Contributor's prior written consent.
8.4 Severability.
If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Notwithstanding the foregoing, if You are prohibited by law
from fully and specifically complying with Sections 2.2 or 3, this
License will immediately terminate and You must immediately discontinue
any use of Covered Code.
8.5 Governing Law.
This License shall be governed by the laws of the United States and the
State of Washington, as applied to contracts entered into and to be
performed in Washington between Washington residents. The application of
the United Nations Convention on Contracts for the International Sale of
Goods is expressly excluded. You agree that the state and federal courts
located in Seattle, Washington have exclusive jurisdiction over any
claim relating to the License, including contract and tort claims.
8.6 Dispute Resolution.
a) Arbitration. Any dispute arising out of or relating to this License
shall be finally settled by arbitration as set out herein, except that
either party may bring any action, in a court of competent jurisdiction
(which jurisdiction shall be exclusive), with respect to any dispute
relating to such party's Intellectual Property Rights or with respect to
Your compliance with the TCK license. Arbitration shall be administered:
(i) by the American Arbitration Association (AAA), (ii) in accordance
with the rules of the United Nations Commission on International Trade
Law (UNCITRAL) (the "Rules") in effect at the time of arbitration as
modified herein; and (iii) the arbitrator will apply the substantive
laws of Washington and the United States. Judgment upon the award
rendered by the arbitrator may be entered in any court having
jurisdiction to enforce such award.
b) Arbitration language, venue and damages. All arbitration proceedings
shall be conducted in English by a single arbitrator selected in
accordance with the Rules, who must be fluent in English and be either a
retired judge or practicing attorney having at least ten (10) years
litigation experience and be reasonably familiar with the technology
matters relative to the dispute. Unless otherwise agreed, arbitration
venue shall be in Seattle, Washington. The arbitrator may award monetary
damages only and nothing shall preclude either party from seeking
provisional or emergency relief from a court of competent jurisdiction.
The arbitrator shall have no authority to award damages in excess of
those permitted in this License and any such award in excess is void.
All awards will be payable in U.S. dollars and may include, for the
prevailing party (i) pre-judgment award interest, (ii) reasonable
attorneys' fees incurred in connection with the arbitration, and (iii)
reasonable costs and expenses incurred in enforcing the award. The
arbitrator will order each party to produce identified documents and
respond to no more than twenty-five single question interrogatories.
8.7 Construction.
Any law or regulation, which provides that the language of a contract
shall be construed against the drafter, shall not apply to this License.
8.8 U.S. Government End Users.
The Covered Code is a "commercial item," as that term is defined in 48
C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer software"
and "commercial computer software documentation," as such terms are used
in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and
48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government
End Users acquire Covered Code with only those rights set forth herein.
You agree to pass this notice to our licensees.
8.9 Marketing Activities.
Licensee hereby grants Original Contributor a non-exclusive,
non-transferable, limited license to use the Licensee's company name and
logo ("Licensee Marks") in any presentations, press releases, or
marketing materials solely for the purpose of identifying Licensee as a
member of the Helix Community. Licensee shall provide samples of
Licensee Marks to Original Contributor upon request by Original
Contributor. Original Contributor acknowledges that the Licensee Marks
are the trademarks of Licensee. Original Contributor shall not use the
Licensee Marks in a way that may imply that Original Contributor is an
agency or branch of Licensee. Original Contributor understands and
agrees that the use of any Licensee Marks in connection with this
Agreement shall not create any right, title or interest, in, or to the
Licensee Marks or any Licensee trademarks and that all such use and
goodwill associated with any such trademarks will inure to the benefit
of Licensee. Further the Original Contributor will stop usage of the
Licensee Marks upon Licensee's request.
8.10 Press Announcements.
You may make press announcements or other public statements regarding
this License without the prior written consent of the Original
Contributor, if Your statement is limited to announcing the licensing of
the Covered Code or the availability of Your Product and its
compatibility with the Covered Code. All other public announcements
regarding this license require the prior written consent of the Original
Contributor. Consent requests are welcome at press@helixcommunity.org.
8.11 International Use.
a) Export/Import laws. Covered Code is subject to U.S. export control
laws and may be subject to export or import regulations in other
countries. Each party agrees to comply strictly with all such laws and
regulations and acknowledges their responsibility to obtain such
licenses to export, re-export, or import as may be required. You agree
to pass these obligations to Your licensees.
b) Intellectual Property Protection. Due to limited intellectual
property protection and enforcement in certain countries, You agree not
to redistribute the Original Code, Upgraded Code, TCK and Specifications
to any country on the list of restricted countries on the Community Web
Server.
8.12 Language.
This License is in the English language only, which language shall be
controlling in all respects, and all versions of this License in any
other language shall be for accommodation only and shall not be binding
on the parties to this License. All communications and notices made or
given pursuant to this License, and all documentation and support to be
provided, unless otherwise noted, shall be in the English language.
PLEASE READ THE TERMS OF THIS LICENSE CAREFULLY. BY CLICKING ON THE
"ACCEPT" BUTTON BELOW YOU ARE ACCEPTING AND AGREEING TO THE TERMS AND
CONDITIONS OF THIS LICENSE WITH REALNETWORKS, INC. IF YOU ARE AGREEING
TO THIS LICENSE ON BEHALF OF A COMPANY, YOU REPRESENT THAT YOU ARE
AUTHORIZED TO BIND THE COMPANY TO SUCH A LICENSE. WHETHER YOU ARE ACTING
ON YOUR OWN BEHALF, OR REPRESENTING A COMPANY, YOU MUST BE OF MAJORITY
AGE AND BE OTHERWISE COMPETENT TO ENTER INTO CONTRACTS. IF YOU DO NOT
MEET THIS CRITERIA OR YOU DO NOT AGREE TO ANY OF THE TERMS AND
CONDITIONS OF THIS LICENSE, CLICK ON THE REJECT BUTTON TO EXIT.
GLOSSARY
1. *"Added Value"* means code which:
(i) has a principal purpose which is substantially different from that
of the stand-alone Technology;
(ii) represents a significant functional and value enhancement to the
Technology;
(iii) operates in conjunction with the Technology; and
(iv) is not marketed as a technology which replaces or substitutes for
the Technology
2. "*Applicable Patent Rights*" mean: (a) in the case where Original
Contributor is the grantor of rights, claims of patents that (i) are now
or hereafter acquired, owned by or assigned to Original Contributor and
(ii) are necessarily infringed by using or making the Original Code or
Upgraded Code, including Modifications provided by Original Contributor,
alone and not in combination with other software or hardware; and (b) in
the case where Licensee is the grantor of rights, claims of patents that
(i) are now or hereafter acquired, owned by or assigned to Licensee and
(ii) are infringed (directly or indirectly) by using or making
Licensee's Modifications or Error Corrections, taken alone or in
combination with Covered Code.
3. "*Application Programming Interfaces (APIs)"* means the interfaces,
associated header files, service provider interfaces, and protocols that
enable a device, application, Operating System, or other program to
obtain services from or make requests of (or provide services in
response to requests from) other programs, and to use, benefit from, or
rely on the resources, facilities, and capabilities of the relevant
programs using the APIs. APIs includes the technical documentation
describing the APIs, the Source Code constituting the API, and any
Header Files used with the APIs.
4. "*Commercial Use*" means any use (internal or external), copying,
sublicensing or distribution (internal or external), directly or
indirectly of Covered Code by You other than Your Research Use of
Covered Code within Your business or organization or in conjunction with
other Licensees with equivalent Research Use rights. Commercial Use
includes any use of the Covered Code for direct or indirect commercial
or strategic gain, advantage or other business purpose. Any Commercial
Use requires execution of Attachment D by You and Original Contributor.
5. "*Community Code*" means the Original Code, Upgraded Code, Error
Corrections, Shared Modifications, or any combination thereof.
6. "*Community Webserver(s)"* means the webservers designated by
Original Contributor for access to the Original Code, Upgraded Code, TCK
and Specifications and for posting Error Corrections and Shared
Modifications.
7. "*Compliant Covered Code*" means Covered Code that complies with the
requirements of the TCK.
8. "*Contributor*" means each Licensee that creates or contributes to
the creation of any Error Correction or Shared Modification.
9. "*Covered Code*" means the Original Code, Upgraded Code,
Modifications, or any combination thereof.
10. "*Error Correction*" means any change made to Community Code which
conforms to the Specification and corrects the adverse effect of a
failure of Community Code to perform any function set forth in or
required by the Specifications.
11. "*Executable*" means Covered Code that has been converted from
Source Code to the preferred form for execution by a computer or digital
processor (e.g. binary form).
12. "*Extension(s)"* means any additional Interfaces developed by or for
You which: (i) are designed for use with the Technology; (ii) constitute
an API for a library of computing functions or services; and (iii) are
disclosed or otherwise made available to third party software developers
for the purpose of developing software which invokes such additional
Interfaces. The foregoing shall not apply to software developed by Your
subcontractors to be exclusively used by You.
13. "*Header File(s)"* means that portion of the Source Code that
provides the names and types of member functions, data members, class
definitions, and interface definitions necessary to implement the APIs
for the Covered Code. Header Files include, files specifically
designated by Original Contributor as Header Files. Header Files do not
include the code necessary to implement the functionality underlying the
Interface.
14. *"Helix DNA Server Technology"* means the program(s) that implement
the Helix Universal Server streaming engine for the Technology as
defined in the Specification.
15. *"Helix DNA Client Technology"* means the Covered Code that
implements the RealOne Player engine as defined in the Specification.
16. *"Helix DNA Producer Technology"* means the Covered Code that
implements the Helix Producer engine as defined in the Specification.
17. *"Helix DNA Technology"* means the Helix DNA Server Technology, the
Helix DNA Client Technology, the Helix DNA Producer Technology and other
Helix technologies designated by Original Contributor.
18. "*Intellectual Property Rights*" means worldwide statutory and
common law rights associated solely with (i) Applicable Patent Rights;
(ii) works of authorship including copyrights, copyright applications,
copyright registrations and "moral rights"; (iii) the protection of
trade and industrial secrets and confidential information; and (iv)
divisions, continuations, renewals, and re-issuances of the foregoing
now existing or acquired in the future.
19. *"Interface*" means interfaces, functions, properties, class
definitions, APIs, Header Files, GUIDs, V-Tables, and/or protocols
allowing one piece of software, firmware or hardware to communicate or
interoperate with another piece of software, firmware or hardware.
20. "*Internal Deployment Use*" means use of Compliant Covered Code
(excluding Research Use) within Your business or organization only by
Your employees and/or agents on behalf of Your business or organization,
but not to provide services, including content distribution, to third
parties, subject to execution of Attachment D by You and Original
Contributor, if required.
21. "*Licensee*" means any party that has entered into and has in effect
a version of this License with Original Contributor.
22. "*MIME type*" means a description of what type of media or other
content is in a file, including by way of example but not limited to
'audio/x-pn-realaudio-plugin.'
23. "*Modification(s)"* means (i) any addition to, deletion from and/or
change to the substance and/or structure of the Covered Code, including
Interfaces; (ii) the combination of any Covered Code and any previous
Modifications; (iii) any new file or other representation of computer
program statements that contains any portion of Covered Code; and/or
(iv) any new Source Code implementing any portion of the Specifications.
24. "*MP3 Patents*" means any patents necessary to make, use or sell
technology implementing any portion of the specification developed by
the Moving Picture Experts Group known as MPEG-1 Audio Layer-3 or MP3,
including but not limited to all past and future versions, profiles,
extensions, parts and amendments relating to the MP3 specification.
25. "*MPEG-4 Patents*" means any patents necessary to make, use or sell
technology implementing any portion of the specification developed by
the Moving Pictures Experts Group known as MPEG-4, including but not
limited to all past and future versions, profiles, extensions, parts and
amendments relating to the MPEG-4 specification.
26. "*Original Code*" means the initial Source Code for the Technology
as described on the Community Web Server.
27. "*Original Contributor*" means RealNetworks, Inc., its affiliates
and its successors and assigns.
28. "*Original Contributor MIME Type*" means the MIME registry, browser
preferences, or local file/protocol associations invoking any Helix DNA
Client-based application, including the RealOne Player, for playback of
RealAudio, RealVideo, other RealMedia MIME types or datatypes (e.g.,
.ram, .rnx, .rpm, .ra, .rm, .rp, .rt, .rf, .prx, .mpe, .rmp, .rmj, .rav,
.rjs, .rmx, .rjt, .rms), and any other Original Contributor-specific or
proprietary MIME types that Original Contributor may introduce in the
future.
29. "*Personal Use*" means use of Covered Code by an individual solely
for his or her personal, private and non-commercial purposes. An
individual's use of Covered Code in his or her capacity as an officer,
employee, member, independent contractor or agent of a corporation,
business or organization (commercial or non-commercial) does not qualify
as Personal Use.
30. "*RealMedia File Format*" means the file format designed and
developed by RealNetworks for storing multimedia data and used to store
RealAudio and RealVideo encoded streams. Valid RealMedia File Format
extensions include: .rm, .rmj, .rmc, .rmvb, .rms.
31. "*RCSL Webpage*" means the RealNetworks Community Source License
webpage located at https://www.helixcommunity.org/content/rcsl or such
other URL that Original Contributor may designate from time to time.
32. "*Reformatted Specifications*" means any revision to the
Specifications which translates or reformats the Specifications (as for
example in connection with Your documentation) but which does not alter,
subset or superset * *the functional or operational aspects of the
Specifications.
33. "*Research Use*" means use and distribution of Covered Code only for
Your Personal Use, research or development use and expressly excludes
Internal Deployment Use and Commercial Use. Research Use also includes
use of Covered Code to teach individuals how to use Covered Code.
34. "*Shared Modifications*" means Modifications that You distribute or
use for a Commercial Use, in addition to any Modifications provided by
You, at Your option, pursuant to Section 2.2, or received by You from a
Contributor pursuant to Section 2.3.
35. "*Source Code*" means the preferred form of the Covered Code for
making modifications to it, including all modules it contains, plus any
associated interface definition files, scripts used to control
compilation and installation of an Executable, or source code
differential comparisons against either the Original Code or another
well known, available Covered Code of the Contributor's choice. The
Source Code can be in a compressed or archival form, provided the
appropriate decompression or de-archiving software is widely available
for no charge.
36. "*Specifications*" means the specifications for the Technology and
other documentation, as designated on the Community Web Server, as may
be revised by Original Contributor from time to time.
37. "*Trademarks*" means Original Contributor's trademarks and logos,
including, but not limited to, RealNetworks, RealAudio, RealVideo,
RealOne, RealSystem, SureStream, Helix, Helix DNA and other trademarks
whether now used or adopted in the future.
38. "*Technology*" means the technology described in Attachment B, and
Upgrades.
39. "*Technology Compatibility Kit"* or *"TCK*" means the test programs,
procedures, acceptance criteria and/or other requirements, designated by
Original Contributor for use in verifying compliance of Covered Code
with the Specifications, in conjunction with the Original Code and
Upgraded Code. Original Contributor may, in its sole discretion and from
time to time, revise a TCK to correct errors and/or omissions and in
connection with Upgrades.
40. "*Upgrade(s)"* means new versions of Technology designated
exclusively by Original Contributor as an "Upgrade" and released by
Original Contributor from time to time under the terms of the License.
41. "*Upgraded Code*" means the Source Code and/or Executables for
Upgrades, possibly including Modifications made by Contributors.
42. *"User's Guide"* means the users guide for the TCK which Original
Contributor makes available to You to provide direction in how to run
the TCK and properly interpret the results, as may be revised by
Original Contributor from time to time.
43. "*You(r)*" means an individual, or a legal entity acting by and
through an individual or individuals, exercising rights either under
this License or under a future version of this License issued pursuant
to Section 4.1. For legal entities, "You(r)" includes any entity that by
majority voting interest controls, is controlled by, or is under common
control with You.
44. "*Your Products*" means any (i) hardware products You distribute
integrating the Covered Code; (ii) any software products You distribute
with the Covered Code that utilize the APIs of the Covered Code; or
(iii) any services You provide using the Covered Code.
ATTACHMENT A
REQUIRED NOTICES
ATTACHMENT A-1
REQUIRED IN ALL CASES
Notice to be included in header file of all Error Corrections and Shared
Modifications:
Portions Copyright 1994-2003 © RealNetworks, Inc. All rights reserved.
The contents of this file, and the files included with this file, are
subject to the current version of RealNetworks Community Source License
Version 1.1 (the "License"). You may not use this file except in
compliance with the License executed by both You and RealNetworks. You
may obtain a copy of the License at *
https://www.helixcommunity.org/content/rcsl.* You may also obtain a copy
of the License by contacting RealNetworks directly. Please see the
License for the rights, obligations and limitations governing use of the
contents of the file.
This file is part of the Helix DNA technology. RealNetworks, Inc., is
the developer of the Original code and owns the copyrights in the
portions it created.
This file, and the files included with this file, are distributed on an
'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
Contributor(s):
_______________________________________________
Technology Compatibility Kit Test Suite(s) Location:
________________________________
ATTACHMENT A-2
SAMPLE LICENSEE CERTIFICATION
"By clicking the `Agree' button below, You certify that You are a
Licensee in good standing under the RealNetworks Community Source
License, ("License") and that Your access, use and distribution of code
and information You may obtain at this site is subject to the License.
If You are not a Licensee under the RealNetworks Community Source
License You agree not to download, copy or use the Helix DNA technology.
ATTACHMENT A-3
REQUIRED STUDENT NOTIFICATION
"This software and related documentation has been obtained by Your
educational institution subject to the RealNetworks Community Source
License. You have been provided access to the software and related
documentation for use only in connection with your course work and
research activities as a matriculated student of Your educational
institution. Any other use is expressly prohibited.
THIS SOFTWARE AND RELATED DOCUMENTATION CONTAINS PROPRIETARY MATERIAL OF
REALNETWORKS, INC, WHICH ARE PROTECTED BY VARIOUS INTELLECTUAL PROPERTY
RIGHTS.
You may not use this file except in compliance with the License. You may
obtain a copy of the License on the web at
https://www.helixcommunity.org/content/rcsl.
*
*
ATTACHMENT B
Description of Technology
Helix DNA, which consists of Helix DNA Client, Helix DNA Server and
Helix DNA Producer.
Description of "Technology"
Helix DNA Technology v1.0 as described on the Community Web Server.
ATTACHMENT C
TECHNOLOGY COMPATIBILITY KIT LICENSE
The following license is effective for the *Helix DNA* Technology
Compatibility Kit - as described on the Community Web Server. The
Technology Compatibility Kit(s) for the Technology specified in
Attachment B may be accessed at the Community Web Server.
1. TCK License.
1.1 Grants to use TCK
Subject to the terms and restrictions set forth below and the
RealNetworks Community Source License, and the Research Use license,
Original Contributor grants to You a worldwide, non-exclusive,
non-transferable license, to the extent of Original Contributor's
Intellectual Property Rights in the TCK (without the right to
sublicense), to use the TCK to develop and test Covered Code.
1.2 TCK Use Restrictions.
You are not authorized to create derivative works of the TCK or use the
TCK to test any implementation of the Specification that is not Covered
Code. You may not publish Your test results or make claims of
comparative compatibility with respect to other implementations of the
Specification. In consideration for the license grant in Section 1.1
above You agree not to develop Your own tests that are intended to
validate conformation with the Specification.
2. Test Results.
You agree to provide to Original Contributor or the third party test
facility if applicable, Your test results that demonstrate that Covered
Code is Compliant Covered Code and that Original Contributor may publish
or otherwise distribute such test results.
PLEASE READ THE TERMS OF THIS LICENSE CAREFULLY. BY CLICKING ON THE
"ACCEPT" BUTTON BELOW YOU ARE ACCEPTING AND AGREEING TO THE TERMS AND
CONDITIONS OF THIS LICENSE WITH THE ORIGINAL CONTRIBUTOR, REALNETWORKS,
INC. IF YOU ARE AGREEING TO THIS LICENSE ON BEHALF OF A COMPANY, YOU
REPRESENT THAT YOU ARE AUTHORIZED TO BIND THE COMPANY TO SUCH A LICENSE.
WHETHER YOU ARE ACTING ON YOUR OWN BEHALF, OR REPRESENTING A COMPANY,
YOU MUST BE OF MAJORITY AGE AND BE OTHERWISE COMPETENT TO ENTER INTO
CONTRACTS. IF YOU DO NOT MEET THIS CRITERIA OR YOU DO NOT AGREE TO ANY
OF THE TERMS AND CONDITIONS OF THIS LICENSE, CLICK ON THE REJECT BUTTON
TO EXIT.
*ACCEPT / REJECT
*
*
*
*To agree to the R&D/academic terms of this license, please register
<https://www.helixcommunity.org/2002/intro/why-register> on the site --
you will then be given a chance to agree to the clickwrap RCSL
<https://reguseronly.helixcommunity.org/2002/clickwrap/rcsl-clickwrap>
R&D License
<https://reguseronly.helixcommunity.org/2002/clickwrap/rcsl-clickwrap>
and gain access to the RCSL-licensed source code. To build or deploy
commercial applications based on the RCSL, you will need to agree to the
Commercial Use license attachments
<https://www.helixcommunity.org/content/rcsl-attachments>*

View file

@ -0,0 +1,518 @@
RealNetworks Public Source License Version 1.0
(Rev. Date October 28, 2002)
1. General Definitions. This License applies to any program or other work which
RealNetworks, Inc., or any other entity that elects to use this license,
("Licensor") makes publicly available and which contains a notice placed by
Licensor identifying such program or work as "Original Code" and stating that it
is subject to the terms of this RealNetworks Public Source License version 1.0
(or subsequent version thereof) ("License"). You are not required to accept this
License. However, nothing else grants You permission to use, copy, modify or
distribute the software or its derivative works. These actions are prohibited by
law if You do not accept this License. Therefore, by modifying, copying or
distributing the software (or any work based on the software), You indicate your
acceptance of this License to do so, and all its terms and conditions. In
addition, you agree to the terms of this License by clicking the Accept button
or downloading the software. As used in this License:
1.1 "Applicable Patent Rights" mean: (a) in the case where Licensor is the
grantor of rights, claims of patents that (i) are now or hereafter acquired,
owned by or assigned to Licensor and (ii) are necessarily infringed by using or
making the Original Code alone and not in combination with other software or
hardware; and (b) in the case where You are the grantor of rights, claims of
patents that (i) are now or hereafter acquired, owned by or assigned to You and
(ii) are infringed (directly or indirectly) by using or making Your
Modifications, taken alone or in combination with Original Code.
1.2 "Compatible Source License" means any one of the licenses listed on Exhibit
B or at https://www.helixcommunity.org/content/complicense or other licenses
specifically identified by Licensor in writing. Notwithstanding any term to the
contrary in any Compatible Source License, any code covered by any Compatible
Source License that is used with Covered Code must be made readily available in
Source Code format for royalty-free use under the terms of the Compatible Source
License or this License.
1.3 "Contributor" means any person or entity that creates or contributes to the
creation of Modifications.
1.4 "Covered Code" means the Original Code, Modifications, the combination of
Original Code and any Modifications, and/or any respective portions thereof.
1.5 "Deploy" means to use, sublicense or distribute Covered Code other than for
Your internal research and development (R&D) and/or Personal Use, and includes
without limitation, any and all internal use or distribution of Covered Code
within Your business or organization except for R&D use and/or Personal Use, as
well as direct or indirect sublicensing or distribution of Covered Code by You
to any third party in any form or manner.
1.6 "Derivative Work" means either the Covered Code or any derivative work under
United States copyright law, and including any work containing or including any
portion of the Covered Code or Modifications, either verbatim or with
modifications and/or translated into another language. Derivative Work also
includes any work which combines any portion of Covered Code or Modifications
with code not otherwise governed by the terms of this License.
1.7 "Externally Deploy" means to Deploy the Covered Code in any way that may be
accessed or used by anyone other than You, used to provide any services to
anyone other than You, or used in any way to deliver any content to anyone other
than You, whether the Covered Code is distributed to those parties, made
available as an application intended for use over a computer network, or used to
provide services or otherwise deliver content to anyone other than You.
1.8. "Interface" means interfaces, functions, properties, class definitions,
APIs, header files, GUIDs, V-Tables, and/or protocols allowing one piece of
software, firmware or hardware to communicate or interoperate with another piece
of software, firmware or hardware.
1.9 "Modifications" mean any addition to, deletion from, and/or change to, the
substance and/or structure of the Original Code, any previous Modifications, the
combination of Original Code and any previous Modifications, and/or any
respective portions thereof. When code is released as a series of files, a
Modification is: (a) any addition to or deletion from the contents of a file
containing Covered Code; and/or (b) any new file or other representation of
computer program statements that contains any part of Covered Code.
1.10 "Original Code" means (a) the Source Code of a program or other work as
originally made available by Licensor under this License, including the Source
Code of any updates or upgrades to such programs or works made available by
Licensor under this License, and that has been expressly identified by Licensor
as such in the header file(s) of such work; and (b) the object code compiled
from such Source Code and originally made available by Licensor under this
License.
1.11 "Personal Use" means use of Covered Code by an individual solely for his or
her personal, private and non-commercial purposes. An individual's use of
Covered Code in his or her capacity as an officer, employee, member, independent
contractor or agent of a corporation, business or organization (commercial or
non-commercial) does not qualify as Personal Use.
1.12 "Source Code" means the human readable form of a program or other work that
is suitable for making modifications to it, including all modules it contains,
plus any associated interface definition files, scripts used to control
compilation and installation of an executable (object code).
1.13 "You" or "Your" means an individual or a legal entity exercising rights
under this License. For legal entities, "You" or "Your" includes any entity
which controls, is controlled by, or is under common control with, You, where
"control" means (a) the power, direct or indirect, to cause the direction or
management of such entity, whether by contract or otherwise, or (b) ownership of
fifty percent (50%) or more of the outstanding shares or beneficial ownership of
such entity.
2. Permitted Uses; Conditions & Restrictions. Subject to the terms and
conditions of this License, Licensor hereby grants You, effective on the date
You accept this License (via downloading or using Covered Code or otherwise
indicating your acceptance of this License), a worldwide, royalty-free,
non-exclusive copyright license, to the extent of Licensor's copyrights cover
the Original Code, to do the following:
2.1 You may reproduce, display, perform, modify and Deploy Covered Code,
provided that in each instance:
(a) You must retain and reproduce in all copies of Original Code the copyright
and other proprietary notices and disclaimers of Licensor as they appear in the
Original Code, and keep intact all notices in the Original Code that refer to
this License;
(b) You must include a copy of this License with every copy of Source Code of
Covered Code and documentation You distribute, and You may not offer or impose
any terms on such Source Code that alter or restrict this License or the
recipients' rights hereunder, except as permitted under Section 6;
(c) You must duplicate, to the extent it does not already exist, the notice in
Exhibit A in each file of the Source Code of all Your Modifications, and cause
the modified files to carry prominent notices stating that You changed the files
and the date of any change;
(d) You must make Source Code of all Your Externally Deployed Modifications
publicly available under the terms of this License, including the license grants
set forth in Section 3 below, for as long as you Deploy the Covered Code or
twelve (12) months from the date of initial Deployment, whichever is longer. You
should preferably distribute the Source Code of Your Deployed Modifications
electronically (e.g. download from a web site); and
(e) if You Deploy Covered Code in object code, executable form only, You must
include a prominent notice, in the code itself as well as in related
documentation, stating that Source Code of the Covered Code is available under
the terms of this License with information on how and where to obtain such
Source Code. You must also include the Object Code Notice set forth in Exhibit A
in the "about" box or other appropriate place where other copyright notices are
placed, including any packaging materials.
2.2 You expressly acknowledge and agree that although Licensor and each
Contributor grants the licenses to their respective portions of the Covered Code
set forth herein, no assurances are provided by Licensor or any Contributor that
the Covered Code does not infringe the patent or other intellectual property
rights of any other entity. Licensor and each Contributor disclaim any liability
to You for claims brought by any other entity based on infringement of
intellectual property rights or otherwise. As a condition to exercising the
rights and licenses granted hereunder, You hereby assume sole responsibility to
secure any other intellectual property rights needed, if any. For example, if a
third party patent license is required to allow You to make, use, sell, import
or offer for sale the Covered Code, it is Your responsibility to acquire such
license(s).
2.3 Subject to the terms and conditions of this License, Licensor hereby grants
You, effective on the date You accept this License (via downloading or using
Covered Code or otherwise indicating your acceptance of this License), a
worldwide, royalty-free, perpetual, non-exclusive patent license under
Licensor's Applicable Patent Rights to make, use, sell, offer for sale and
import the Covered Code, provided that in each instance you comply with the
terms of this License.
3. Your Grants. In consideration of, and as a condition to, the licenses granted
to You under this License:
(a) You grant to Licensor and all third parties a non-exclusive, perpetual,
irrevocable, royalty free license under Your Applicable Patent Rights and other
intellectual property rights owned or controlled by You, to make, sell, offer
for sale, use, import, reproduce, display, perform, modify, distribute and
Deploy Your Modifications of the same scope and extent as Licensor's licenses
under Sections 2.1 and 2.2; and
(b) You grant to Licensor and its subsidiaries a non-exclusive, worldwide,
royalty-free, perpetual and irrevocable license, under Your Applicable Patent
Rights and other intellectual property rights owned or controlled by You, to
make, use, sell, offer for sale, import, reproduce, display, perform,
distribute, modify or have modified (for Licensor and/or its subsidiaries),
sublicense and distribute Your Modifications, in any form and for any purpose,
through multiple tiers of distribution.
(c) You agree not use any information derived from Your use and review of the
Covered Code, including but not limited to any algorithms or inventions that may
be contained in the Covered Code, for the purpose of asserting any of Your
patent rights, or assisting a third party to assert any of its patent rights,
against Licensor or any Contributor.
4. Derivative Works. You may create a Derivative Work by combining Covered Code
with other code not otherwise governed by the terms of this License and
distribute the Derivative Work as an integrated product. In each such instance,
You must make sure the requirements of this License are fulfilled for the
Covered Code or any portion thereof, including all Modifications.
4.1 You must cause any Derivative Work that you distribute, publish or
Externally Deploy, that in whole or in part contains or is derived from the
Covered Code or any part thereof, to be licensed as a whole at no charge to all
third parties under the terms of this License and no other license except as
provided in Section 4.2. You also must make Source Code available for the
Derivative Work under the same terms as Modifications, described in Sections 2
and 3, above.
4.2 Compatible Source Licenses. Software modules that have been independently
developed without any use of Covered Code and which contain no portion of the
Covered Code, Modifications or other Derivative Works, but are used or combined
in any way wtih the Covered Code or any Derivative Work to form a larger
Derivative Work, are exempt from the conditions described in Section 4.1 but
only to the extent that: the software module, including any software that is
linked to, integrated with, or part of the same applications as, the software
module by any method must be wholly subject to one of the Compatible Source
Licenses. Notwithstanding the foregoing, all Covered Code must be subject to the
terms of this License. Thus, the entire Derivative Work must be licensed under a
combination of the RPSL (for Covered Code) and a Compatible Source License for
any independently developed software modules within the Derivative Work. The
foregoing requirement applies even if the Compatible Source License would
ordinarily allow the software module to link with, or form larger works with,
other software that is not subject to the Compatible Source License. For
example, although the Mozilla Public License v1.1 allows Mozilla code to be
combined with proprietary software that is not subject to the MPL, if
MPL-licensed code is used with Covered Code the MPL-licensed code could not be
combined or linked with any code not governed by the MPL. The general intent of
this section 4.2 is to enable use of Covered Code with applications that are
wholly subject to an acceptable open source license. You are responsible for
determining whether your use of software with Covered Code is allowed under Your
license to such software.
4.3 Mere aggregation of another work not based on the Covered Code with the
Covered Code (or with a work based on the Covered Code) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License. If You deliver the Covered Code for combination and/or integration with
an application previously provided by You (for example, via automatic updating
technology), such combination and/or integration constitutes a Derivative Work
subject to the terms of this License.
5. Exclusions From License Grant. Nothing in this License shall be deemed to
grant any rights to trademarks, copyrights, patents, trade secrets or any other
intellectual property of Licensor or any Contributor except as expressly stated
herein. No right is granted to the trademarks of Licensor or any Contributor
even if such marks are included in the Covered Code. Nothing in this License
shall be interpreted to prohibit Licensor from licensing under different terms
from this License any code that Licensor otherwise would have a right to
license. Modifications, Derivative Works and/or any use or combination of
Covered Code with other technology provided by Licensor or third parties may
require additional patent licenses from Licensor which Licensor may grant in its
sole discretion. No patent license is granted separate from the Original Code or
combinations of the Original Code with other software or hardware.
5.1. Trademarks. This License does not grant any rights to use the trademarks or
trade names owned by Licensor ("Licensor Marks" defined in Exhibit C) or to any
trademark or trade name belonging to any Contributor. No Licensor Marks may be
used to endorse or promote products derived from the Original Code other than as
permitted by the Licensor Trademark Policy defined in Exhibit C.
6. Additional Terms. You may choose to offer, and to charge a fee for, warranty,
support, indemnity or liability obligations and/or other rights consistent with
the scope of the license granted herein ("Additional Terms") to one or more
recipients of Covered Code. However, You may do so only on Your own behalf and
as Your sole responsibility, and not on behalf of Licensor or any Contributor.
You must obtain the recipient's agreement that any such Additional Terms are
offered by You alone, and You hereby agree to indemnify, defend and hold
Licensor and every Contributor harmless for any liability incurred by or claims
asserted against Licensor or such Contributor by reason of any such Additional
Terms.
7. Versions of the License. Licensor may publish revised and/or new versions of
this License from time to time. Each version will be given a distinguishing
version number. Once Original Code has been published under a particular version
of this License, You may continue to use it under the terms of that version. You
may also choose to use such Original Code under the terms of any subsequent
version of this License published by Licensor. No one other than Licensor has
the right to modify the terms applicable to Covered Code created under this
License.
8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in part
pre-release, untested, or not fully tested works. The Covered Code may contain
errors that could cause failures or loss of data, and may be incomplete or
contain inaccuracies. You expressly acknowledge and agree that use of the
Covered Code, or any portion thereof, is at Your sole and entire risk. THE
COVERED CODE IS PROVIDED "AS IS" AND WITHOUT WARRANTY, UPGRADES OR SUPPORT OF
ANY KIND AND LICENSOR AND LICENSOR'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS
"LICENSOR" FOR THE PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY
DISCLAIM ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF
SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET
ENJOYMENT, AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. LICENSOR AND EACH
CONTRIBUTOR DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE
COVERED CODE, THAT THE FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR
REQUIREMENTS, THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR
ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO ORAL OR
WRITTEN DOCUMENTATION, INFORMATION OR ADVICE GIVEN BY LICENSOR, A LICENSOR
AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. You
acknowledge that the Covered Code is not intended for use in high risk
activities, including, but not limited to, the design, construction, operation
or maintenance of nuclear facilities, aircraft navigation, aircraft
communication systems, or air traffic control machines in which case the failure
of the Covered Code could lead to death, personal injury, or severe physical or
environmental damage. Licensor disclaims any express or implied warranty of
fitness for such uses.
9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT
SHALL LICENSOR OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING TO THIS LICENSE OR
YOUR USE OR INABILITY TO USE THE COVERED CODE, OR ANY PORTION THEREOF, WHETHER
UNDER A THEORY OF CONTRACT, WARRANTY, TORT (INCLUDING NEGLIGENCE OR STRICT
LIABILITY), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF LICENSOR OR SUCH
CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND
NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME
JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF INCIDENTAL OR
CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event
shall Licensor's total liability to You for all damages (other than as may be
required by applicable law) under this License exceed the amount of ten dollars
($10.00).
10. Ownership. Subject to the licenses granted under this License, each
Contributor retains all rights, title and interest in and to any Modifications
made by such Contributor. Licensor retains all rights, title and interest in and
to the Original Code and any Modifications made by or on behalf of Licensor
("Licensor Modifications"), and such Licensor Modifications will not be
automatically subject to this License. Licensor may, at its sole discretion,
choose to license such Licensor Modifications under this License, or on
different terms from those contained in this License or may choose not to
license them at all.
11. Termination.
11.1 Term and Termination. The term of this License is perpetual unless
terminated as provided below. This License and the rights granted hereunder will
terminate:
(a) automatically without notice from Licensor if You fail to comply with any
term(s) of this License and fail to cure such breach within 30 days of becoming
aware of such breach;
(b) immediately in the event of the circumstances described in Section 12.5(b);
or
(c) automatically without notice from Licensor if You, at any time during the
term of this License, commence an action for patent infringement against
Licensor (including by cross-claim or counter claim in a lawsuit);
(d) upon written notice from Licensor if You, at any time during the term of
this License, commence an action for patent infringement against any third party
alleging that the Covered Code itself (excluding combinations with other
software or hardware) infringes any patent (including by cross-claim or counter
claim in a lawsuit).
11.2 Effect of Termination. Upon termination, You agree to immediately stop any
further use, reproduction, modification, sublicensing and distribution of the
Covered Code and to destroy all copies of the Covered Code that are in your
possession or control. All sublicenses to the Covered Code which have been
properly granted prior to termination shall survive any termination of this
License. Provisions which, by their nature, should remain in effect beyond the
termination of this License shall survive, including but not limited to Sections
3, 5, 8, 9, 10, 11, 12.2 and 13. No party will be liable to any other for
compensation, indemnity or damages of any sort solely as a result of terminating
this License in accordance with its terms, and termination of this License will
be without prejudice to any other right or remedy of any party.
12. Miscellaneous.
12.1 Government End Users. The Covered Code is a "commercial item" as defined in
FAR 2.101. Government software and technical data rights in the Covered Code
include only those rights customarily provided to the public as defined in this
License. This customary commercial license in technical data and software is
provided in accordance with FAR 12.211 (Technical Data) and 12.212 (Computer
Software) and, for Department of Defense purchases, DFAR 252.227-7015 (Technical
Data -- Commercial Items) and 227.7202-3 (Rights in Commercial Computer Software
or Computer Software Documentation). Accordingly, all U.S. Government End Users
acquire Covered Code with only those rights set forth herein.
12.2 Relationship of Parties. This License will not be construed as creating an
agency, partnership, joint venture or any other form of legal association
between or among You, Licensor or any Contributor, and You will not represent to
the contrary, whether expressly, by implication, appearance or otherwise.
12.3 Independent Development. Nothing in this License will impair Licensor's
right to acquire, license, develop, have others develop for it, market and/or
distribute technology or products that perform the same or similar functions as,
or otherwise compete with, Modifications, Derivative Works, technology or
products that You may develop, produce, market or distribute.
12.4 Waiver; Construction. Failure by Licensor or any Contributor to enforce any
provision of this License will not be deemed a waiver of future enforcement of
that or any other provision. Any law or regulation which provides that the
language of a contract shall be construed against the drafter will not apply to
this License.
12.5 Severability. (a) If for any reason a court of competent jurisdiction finds
any provision of this License, or portion thereof, to be unenforceable, that
provision of the License will be enforced to the maximum extent permissible so
as to effect the economic benefits and intent of the parties, and the remainder
of this License will continue in full force and effect. (b) Notwithstanding the
foregoing, if applicable law prohibits or restricts You from fully and/or
specifically complying with Sections 2 and/or 3 or prevents the enforceability
of either of those Sections, this License will immediately terminate and You
must immediately discontinue any use of the Covered Code and destroy all copies
of it that are in your possession or control.
12.6 Dispute Resolution. Any litigation or other dispute resolution between You
and Licensor relating to this License shall take place in the Seattle,
Washington, and You and Licensor hereby consent to the personal jurisdiction of,
and venue in, the state and federal courts within that District with respect to
this License. The application of the United Nations Convention on Contracts for
the International Sale of Goods is expressly excluded.
12.7 Export/Import Laws. This software is subject to all export and import laws
and restrictions and regulations of the country in which you receive the Covered
Code and You are solely responsible for ensuring that You do not export,
re-export or import the Covered Code or any direct product thereof in violation
of any such restrictions, laws or regulations, or without all necessary
authorizations.
12.8 Entire Agreement; Governing Law. This License constitutes the entire
agreement between the parties with respect to the subject matter hereof. This
License shall be governed by the laws of the United States and the State of
Washington.
Where You are located in the province of Quebec, Canada, the following clause
applies: The parties hereby confirm that they have requested that this License
and all related documents be drafted in English. Les parties ont exig&eacute;
que le pr&eacute;sent contrat et tous les documents connexes soient
r&eacute;dig&eacute;s en anglais.
EXHIBIT A.
"Copyright &copy; 1995-2002
RealNetworks, Inc. and/or its licensors. All Rights Reserved.
The contents of this file, and the files included with this file, are subject to
the current version of the RealNetworks Public Source License Version 1.0 (the
"RPSL") available at https://www.helixcommunity.org/content/rpsl unless you have
licensed the file under the RealNetworks Community Source License Version 1.0
(the "RCSL") available at https://www.helixcommunity.org/content/rcsl, in which
case the RCSL will apply. You may also obtain the license terms directly from
RealNetworks. You may not use this file except in compliance with the RPSL or,
if you have a valid RCSL with RealNetworks applicable to this file, the RCSL.
Please see the applicable RPSL or RCSL for the rights, obligations and
limitations governing use of the contents of the file.
This file is part of the Helix DNA Technology. RealNetworks is the developer of
the Original code and owns the copyrights in the portions it created.
This file, and the files included with this file, is distributed and made
available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR
IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING
WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
Contributor(s): ____________________________________
Technology Compatibility Kit Test
Suite(s) Location (if licensed under the RCSL): ______________________________
Object Code Notice: Helix DNA Client technology included. Copyright (c)
RealNetworks, Inc., 1995-2002. All rights reserved.
EXHIBIT B
Compatible Source Licenses for the RealNetworks Public Source License. The
following list applies to the most recent version of the license as of October
25, 2002, unless otherwise indicated.
* Academic Free License
* Apache Software License
* Apple Public Source License
* Artistic license
* Attribution Assurance Licenses
* BSD license
* Common Public License (1)
* Eiffel Forum License
* GNU General Public License (GPL) (1)
* GNU Library or "Lesser" General Public License (LGPL) (1)
* IBM Public License
* Intel Open Source License
* Jabber Open Source License
* MIT license
* MITRE Collaborative Virtual Workspace License (CVW License)
* Motosoto License
* Mozilla Public License 1.0 (MPL)
* Mozilla Public License 1.1 (MPL)
* Nokia Open Source License
* Open Group Test Suite License
* Python Software Foundation License
* Ricoh Source Code Public License
* Sun Industry Standards Source License (SISSL)
* Sun Public License
* University of Illinois/NCSA Open Source License
* Vovida Software License v. 1.0
* W3C License
* X.Net License
* Zope Public License
* zlib/libpng license
(1) Note: because this license contains certain reciprocal licensing terms that
purport to extend to independently developed code, You may be prohibited under
the terms of this otherwise compatible license from using code licensed under
its terms with Covered Code because Covered Code may only be licensed under the
RealNetworks Public Source License. Any attempt to apply non RPSL license terms,
including without limitation the GPL, to Covered Code is expressly forbidden.
You are responsible for ensuring that Your use of Compatible Source Licensed
code does not violate either the RPSL or the Compatible Source License.
The latest version of this list can be found at:
https://www.helixcommunity.org/content/complicense
EXHIBIT C
RealNetworks' Trademark policy.
RealNetworks defines the following trademarks collectively as "Licensor
Trademarks": "RealNetworks", "RealPlayer", "RealJukebox", "RealSystem",
"RealAudio", "RealVideo", "RealOne Player", "RealMedia", "Helix" or any other
trademarks or trade names belonging to RealNetworks.
RealNetworks "Licensor Trademark Policy" forbids any use of Licensor Trademarks
except as permitted by and in strict compliance at all times with RealNetworks'
third party trademark usage guidelines which are posted at
http://www.realnetworks.com/info/helixlogo.html.

View file

@ -0,0 +1,80 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: RCSL 1.0/RPSL 1.0
#
# Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved.
#
# The contents of this file, and the files included with this file, are
# subject to the current version of the RealNetworks Public Source License
# Version 1.0 (the "RPSL") available at
# http://www.helixcommunity.org/content/rpsl unless you have licensed
# the file under the RealNetworks Community Source License Version 1.0
# (the "RCSL") available at http://www.helixcommunity.org/content/rcsl,
# in which case the RCSL will apply. You may also obtain the license terms
# directly from RealNetworks. You may not use this file except in
# compliance with the RPSL or, if you have a valid RCSL with RealNetworks
# applicable to this file, the RCSL. Please see the applicable RPSL or
# RCSL for the rights, obligations and limitations governing use of the
# contents of the file.
#
# This file is part of the Helix DNA Technology. RealNetworks is the
# developer of the Original Code and owns the copyrights in the portions
# it created.
#
# This file, and the files included with this file, is distributed and made
# available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
# EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES,
# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
#
# Technology Compatibility Kit Test Suite(s) Location:
# http://www.helixcommunity.org/content/tck
#
# Contributor(s):
#
# ***** END LICENSE BLOCK *****
#
UmakefileVersion(2,1)
# old C++ shim layer for backwards compatibility with Player
# if you don't want to use this, just use the public C api
# in mp3dec.c/.h
project.AddSources("mpadecobj.cpp")
project.AddSources("mp3dec.c", "mp3tabs.c")
if (sysinfo.arch == 'arm') and project.IsDefined('HELIX_FEATURE_USE_IPP4'):
project.AddDefines('USE_IPP_MP3')
if ('USE_IPP_MP3' in project.defines):
project.AddSources("ipp/bitstream.c", "ipp/buffers.c", "ipp/dequant.c",
"ipp/huffman.c", "ipp/imdct.c", "ipp/subband.c")
if('_LINUX' in project.defines):
project.AddIncludes('%s/include/' % GetSDKPath("ipp_mp3_tools"))
else:
project.AddIncludes('\"%s\include\"' % GetSDKPath("ipp_mp3_tools"))
else:
project.AddSources("real/bitstream.c", "real/buffers.c", "real/dct32.c",
"real/dequant.c", "real/dqchan.c", "real/huffman.c", "real/hufftabs.c",
"real/imdct.c", "real/scalfact.c",
"real/stproc.c", "real/subband.c", "real/trigtabs.c")
if ('ARM_ADS' in project.defines):
project.AddSources("real/arm/asmpoly.s")
elif ('_WINCE' in project.defines and '_ARM' in project.defines):
project.AddSources("real/arm/asmpoly.s", "real/arm/asmmisc.s")
elif (('_SYMBIAN' in project.defines or '_LINUX' in project.defines ) and
'ARM' in project.defines):
project.AddSources("real/arm/asmpoly_gcc.s")
else:
project.AddSources("real/polyphase.c")
project.AddIncludes("./pub")
project.AddModuleIncludes("common/include")
project.AddModuleIncludes("common/runtime/pub")
LibraryTarget("mp3codecfixpt")
DependTarget()

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