-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Description
Related area
Zigbee, NFC tag reader, DAC Decoder, Micro Mini SD Storage Expansion Board
Hardware specification
ESP32-h2, PN532 I2C mode, I2S PCM5102 DAC Decoder, Micro Mini SD Storage Expansion Board
Describe the solution you'd like
Hello. Over the past 3 weeks I'm looking for a way to make this setup:
My idea is to make a Zigbee coordinator (ESP32-h2) that can:
- communicate to Home Assistant(HA) uzing Zigbee (HA uses Zigbee2Mqtt).
- scan NFC tags using the I2C mode of the "PN532 NFC RFID Module 13.56MHz V3".
- play music using external PCM5102 DAC decoder (using I2S protocos).
- the music will be stored inside a external Micro Mini SD Storage Expansion Board to different playlists. Maybe like that: Playlist 1 in folder "ACDC", Playlist 2 in folder "Metallica" (using SPI protocols)
Steps:
-
Connect ESP32-h2 to HA - using Zigbee - DONE (library: espressif/arduino-esp32)
-
Connect ESP32-h2 to PN532 NFC - using I2C - DONE (library: adafruit/Adafruit-PN532)
By following this video
################
################ Q: 1 ### How to populate the TAG ID to Home Assistant using Zigbee ? ################
################
If you can't store it here:

Maybe just add it as:

There are 2 options to make the tag reader work:
- If tag # 1,2,3,... detected, do task # 1,2,3,... - Simple and I will skip it
- If tag # 1,2,3,... detected, pass the tag id to the main Zigbee controller (my HA brain that controlls all my automation). Than based on some automations will return to the ESP32-h2 a response and based on that response do task # 1,2,3,...
################
################ Q: 2### After Q1 is done, how do i return some data to ESP32-h2 ? ################
################
-
Connect ESP32-h2 to PCM5102 DAC Decoder - using I2S - DONE (following XTronical youtube videos)
I added a WavData.h for now until i implement the SD Expansion Board
WavData.h containes 16 bit mono (16000Hz) or 16 bit steroe (16000Hz) made from Audacity and HxD programs.
with code starting like that:
unsigned const char WavData16BitMono[288982] = { 0x52, 0x49, 0x46, 0x46, 0xCE, 0x68, 0x04...
Will remove it later. -
Connect ESP32-h2 to Micro Mini SD Storage Expansion Board - using SPI - TODO
-
Added a pasive Buzzer - DONE
-
Added a vibrating motor - DONE
Can someone help me with my Q? I think that adding custom Exposes (or NFC Tag) to Zigbee devices will be a good addition to this libraly. That way I can even triger an automation to play music from just opening a door or reading a tag.
Note* In the end I will provite the full steps and code!
Here is the code so far:
// ************************************** DAC **************************************
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h" // Library of I2S routines, comes with ESP32 standard install
#include "WavData.h" // The Wav file stored in memory, should be in folder with this file
static const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number
unsigned const char* TheData;
uint32_t DataIdx=0; // index offset into "TheData" for current data t send to I2S
bool isMusicPlaying = false;
struct WavHeader_Struct
{
// RIFF Section
char RIFFSectionID[4]; // Letters "RIFF"
uint32_t Size; // Size of entire file less 8
char RiffFormat[4]; // Letters "WAVE"
// Format Section
char FormatSectionID[4]; // letters "fmt"
uint32_t FormatSize; // Size of format section less 8
uint16_t FormatID; // 1=uncompressed PCM
uint16_t NumChannels; // 1=mono,2=stereo
uint32_t SampleRate; // 44100, 16000, 8000 etc.
uint32_t ByteRate; // =SampleRate * Channels * (BitsPerSample/8)
uint16_t BlockAlign; // =Channels * (BitsPerSample/8), effectivly the size of a single sample for all chans.
uint16_t BitsPerSample; // 8,16,24 or 32
// Data Section
char DataSectionID[4]; // The letters "data"
uint32_t DataSize; // Size of the data that follows
}WavHeader;
// I2S configuration structures
static const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = 44100, // Note, this will be changed later
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority
.dma_buf_count = 8, // 8 buffers
.dma_buf_len = 1024, // 1K per buffer, so 8K of buffer space
.use_apll=0,
.tx_desc_auto_clear= true,
.fixed_mclk=-1
};
static const i2s_pin_config_t pin_config = {
.bck_io_num = 10, // The bit clock connectiom, goes to pin 27 of ESP32
.ws_io_num = 25, // Word select, also known as word select or left right clock
.data_out_num = 12, // Data out from the ESP32, connect to DIN on 38357A
.data_in_num = I2S_PIN_NO_CHANGE // we are not interested in I2S data into the ESP32
};
TaskHandle_t musicTaskHandle;
void musicTask(void* parameter) {
while (true) {
DAC_start();
}
}
void DAC_setup() {
const unsigned char *WavFile=WavData16BitMono;
// const unsigned char *WavFile=WavData16BitStereo;
Serial.begin(115200);
memcpy(&WavHeader,WavFile,44); // Copy the header part of the wav data into our structure
DumpWAVHeader(&WavHeader); // Dump the header data to serial, optional!
if(ValidWavData(&WavHeader))
{
Serial.println("Initializing DAC...");
i2s_driver_install(i2s_num, &i2s_config, 0, NULL); // ESP32 will allocated resources to run I2S
Serial.println("I2S Driver Installed");
i2s_set_pin(i2s_num, &pin_config); // Tell it the pins you will be using
i2s_set_sample_rates(i2s_num, WavHeader.SampleRate); //set sample rate
TheData=WavFile+44; // set to start of data
}
else {
Serial.println("Invalid WAV file. Exiting setup...");
return; // Or handle the error gracefully
}
}
void DAC_start() {
uint8_t Mono[4]; // This holds the data we actually send to the I2S if mono sound
const unsigned char *Data; // Points to the data we are going to send
size_t BytesWritten; // Returned by the I2S write routine, we are not interested in it
// The WAV Data could be mono or stereo but always 16 bit, that's a data size of 2 byte or 4 bytes
// Unfortunatly I2S only allows stereo, so to send mono we have to send the mono sample on both left and right
// channels. It's a bit of a faf really!
if(WavHeader.NumChannels==1) // mono
{
Mono[0]=*(TheData+DataIdx); // copy the sample to both left and right samples, this is left
Mono[1]=*(TheData+DataIdx+1);
Mono[2]=*(TheData+DataIdx); // Same data to the right channel
Mono[3]=*(TheData+DataIdx+1);
Data=Mono;
}
else // stereo
Data=TheData+DataIdx;
i2s_write(i2s_num,Data,4,&BytesWritten,portMAX_DELAY);
DataIdx+=WavHeader.BlockAlign; // increase the data index to next next sample
if(DataIdx>=WavHeader.DataSize) { // If we gone past end of data reset back to beginning
DataIdx=0;
// After breaking out of the loop, delete the task
vTaskDelete(NULL); // Pass NULL to delete the current task (musicTask)
}
}
// ************************************** Micro Mini SD Storage Expansion Board *****************************
// // **************************************** factory reset ****************************************
#define BUTTON_PIN 9 // ESP32-C6/H2 Boot button
// Init button for factory reset
void factoryReset_setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
}
// **************************************** Zigbee ****************************************
#ifndef ZIGBEE_MODE_ZCZR
#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"
#endif
#include "Zigbee.h"
#define LED_PIN RGB_BUILTIN
#define ZIGBEE_LIGHT_ENDPOINT 10
ZigbeeLight zbLight = ZigbeeLight(ZIGBEE_LIGHT_ENDPOINT);
void setLED(bool value) {
Serial.print("TEST");
Serial.println(value ? "1" : "0");
digitalWrite(LED_PIN, value);
}
void ZIGBEE_setup() {
// Init LED and turn it OFF (if LED_PIN == RGB_BUILTIN, the rgbLedWrite() will be used under the hood)
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
//Optional: set Zigbee device name and model
zbLight.setManufacturerAndModel("Espressif", "ZBLightBulb");
// Set callback function for light change
zbLight.onLightChange(setLED);
//Add endpoint to Zigbee Core
log_d("Adding ZigbeeLight endpoint to Zigbee Core");
Zigbee.addEndpoint(&zbLight);
// When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE
log_d("Calling Zigbee.begin()");
Zigbee.begin();
}
// ************************************** NFC CARD READER *****************************
// NFC Card Reader
#include <Wire.h>
#include <Adafruit_PN532.h>
#define PN532_IRQ (2)
#define PN532_RESET (3)
#define SDA_PIN 4 // Choose SDA pin
#define SCL_PIN 5 // Choose SCL pin
unsigned long nfcCheckInterval = 2000; // Interval for checking NFC (in milliseconds)
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
void NFC_setup() {
Wire.begin(SDA_PIN, SCL_PIN); // Initializes I2C communication
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (! versiondata) {
Serial.print("Didn't find PN53x board");
while (1); // halt
}
// Got ok data, print it out!
Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
// configure board to read FRID tags
nfc.SAMConfig();
Serial.println("Waiting for an ISO14443A Card ...");
}
// Global variables to store the last scanned UID
uint8_t lastUid[7] = {0}; // Buffer to store the last UID
uint8_t lastUidLength = 0; // Length of the last UID
// Function to compare two UIDs
bool isSameUID(uint8_t* uid1, uint8_t length1, uint8_t* uid2, uint8_t length2) {
if (length1 != length2) {
return false; // Different length, so UIDs are different
}
for (int i = 0; i < length1; i++) {
if (uid1[i] != uid2[i]) {
return false; // Found different byte, so UIDs are different
}
}
return true; // UIDs are the same
}
// ************************************** BUZZER *****************************
const int buzzerPin = 22; // "+" passive buzzer
int buzzerMelody[] = { 262, 392, 523, 440, 466, 466, 440, 392, 523, 440, 523, 440, 349, 392, 523, 523, 523 };
int noteDurations[] = { 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 };
// Start playing the buzzer (initialize the state)
void startBuzzer() {
for (int i = 0; i < sizeof(buzzerMelody) / sizeof(buzzerMelody[0]); i++) {
// Play the current note
tone(buzzerPin, buzzerMelody[i], noteDurations[i]);
// Wait for the note to finish playing
delay(noteDurations[i]);
}
// After the melody is finished, stop the buzzer
noTone(buzzerPin);
}
void BUZZER_setup() {
pinMode(buzzerPin, OUTPUT);
noTone(buzzerPin); // Ensure buzzer is off at the start
}
// ************************************** VIBRATOR *****************************
const int vibratorPin = 1; // "+" passive buzzer
// Variables for managing vibrator timing
unsigned long vibratorStartTime = 0;
unsigned long vibratorCycleStartTime = 0;
bool isVibratorOn = false;
bool isVibratorActive = false; // Tracks whether the vibrator should be active
// Start the vibrator (initialize the state)
void startVibrator() {
digitalWrite(vibratorPin, HIGH);
delay(500);
digitalWrite(vibratorPin, LOW);
}
void VIBRATOR_setup() {
pinMode(vibratorPin, OUTPUT);
noTone(vibratorPin); // Ensure buzzer is off at the start
}
void setup() {
Serial.begin(115200);
while (!Serial) delay(1000);
factoryReset_setup();
ZIGBEE_setup();
NFC_setup();
BUZZER_setup();
VIBRATOR_setup();
DAC_setup();
Serial.println("Hello from setup!");
}
void loop() {
Serial.println("Hello from loop!");
// Checking button for factory reset
if (digitalRead(BUTTON_PIN) == LOW) { // Push button pressed
// Key debounce handling
delay(100);
int startTime = millis();
while (digitalRead(BUTTON_PIN) == LOW) {
delay(50);
if ((millis() - startTime) > 3000) {
// If key pressed for more than 3secs, factory reset Zigbee and reboot
Serial.printf("Resetting Zigbee to factory settings, reboot.\n");
Zigbee.factoryReset();
}
}
} else {
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the UID
uint8_t uidLength; // Length of the UID
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
if (success) {
// Compare current UID with the last scanned UID
if (isSameUID(uid, uidLength, lastUid, lastUidLength)) {
// If the same UID as the last scan, skip processing
// Serial.println("Same UID detected, skipping...");
delay(nfcCheckInterval); // Wait for 1 second before repeating (to avoid overloading the DAC)k
} else {
isMusicPlaying = true;
// Save the current UID as the last UID
memcpy(lastUid, uid, uidLength); // Copy current UID to lastUid
lastUidLength = uidLength; // Save the length of the current UID
// When an NFC card is detected, start both the melody and the vibrator
// Display some basic information about the card
Serial.println("Found an ISO14443A card");
Serial.print(" UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
Serial.print(" UID Value: ");
nfc.PrintHex(uid, uidLength);
startBuzzer();
startVibrator();
// Create a task to run DAC_start() continuously
xTaskCreate(musicTask, "Music Task", 4096, NULL, 1, &musicTaskHandle);
delay(nfcCheckInterval);
}
}
}
}
bool ValidWavData(WavHeader_Struct* Wav)
{
if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0)
{
Serial.print("Invlaid data - Not RIFF format");
return false;
}
if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
{
Serial.print("Invlaid data - Not Wave file");
return false;
}
if(memcmp(Wav->FormatSectionID,"fmt",3)!=0)
{
Serial.print("Invlaid data - No format section found");
return false;
}
if(memcmp(Wav->DataSectionID,"data",4)!=0)
{
Serial.print("Invlaid data - data section not found");
return false;
}
if(Wav->FormatID!=1)
{
Serial.print("Invlaid data - format Id must be 1");
return false;
}
if(Wav->FormatSize!=16)
{
Serial.print("Invlaid data - format section size must be 16.");
return false;
}
if((Wav->NumChannels!=1)&(Wav->NumChannels!=2))
{
Serial.print("Invlaid data - only mono or stereo permitted.");
return false;
}
if(Wav->SampleRate>48000)
{
Serial.print("Invlaid data - Sample rate cannot be greater than 48000");
return false;
}
if(Wav->BitsPerSample!=16)
{
Serial.print("Invlaid data - Only 16 bits per sample permitted.");
return false;
}
return true;
}
void DumpWAVHeader(WavHeader_Struct* Wav)
{
if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0)
{
Serial.print("Not a RIFF format file - ");
PrintData(Wav->RIFFSectionID,4);
return;
}
if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
{
Serial.print("Not a WAVE file - ");
PrintData(Wav->RiffFormat,4);
return;
}
if(memcmp(Wav->FormatSectionID,"fmt",3)!=0)
{
Serial.print("fmt ID not present - ");
PrintData(Wav->FormatSectionID,3);
return;
}
if(memcmp(Wav->DataSectionID,"data",4)!=0)
{
Serial.print("data ID not present - ");
PrintData(Wav->DataSectionID,4);
return;
}
// All looks good, dump the data
Serial.print("Total size :");Serial.println(Wav->Size);
Serial.print("Format section size :");Serial.println(Wav->FormatSize);
Serial.print("Wave format :");Serial.println(Wav->FormatID);
Serial.print("Channels :");Serial.println(Wav->NumChannels);
Serial.print("Sample Rate :");Serial.println(Wav->SampleRate);
Serial.print("Byte Rate :");Serial.println(Wav->ByteRate);
Serial.print("Block Align :");Serial.println(Wav->BlockAlign);
Serial.print("Bits Per Sample :");Serial.println(Wav->BitsPerSample);
Serial.print("Data Size :");Serial.println(Wav->DataSize);
}
void PrintData(const char* Data,uint8_t NumBytes)
{
for(uint8_t i=0;i<NumBytes;i++)
Serial.print(Data[i]);
Serial.println();
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
