Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why not move mp3client to AsyncTCP as well? #2

Closed
me-no-dev opened this issue Apr 26, 2016 · 26 comments
Closed

Why not move mp3client to AsyncTCP as well? #2

me-no-dev opened this issue Apr 26, 2016 · 26 comments

Comments

@me-no-dev
Copy link

Since I see you already have the Async server up (yeah! :)), why not move the mp3 stream to async as well? You will not need to buffer the client and you can write to the VS1053 as data comes. Using that will also ACK the data only after you have it loaded to the VS, which will make networking a little more predictable :)
Let me know what you think, I can help if needed :)

@Edzelf
Copy link
Owner

Edzelf commented Apr 26, 2016

I will try that.

@Edzelf
Copy link
Owner

Edzelf commented Apr 26, 2016

Hello,

I 've tried it. But is was no success.
It looks like the client misses some bytes now and then.
In de debug output I see in the original version:
D: ICY 200 OK
D: icy-notice1:
This stream requires Winamp

D: icy-notice2:SHOUTcast Distributed Network Audio Server/Linux v1.9.8

D: icy-name:NAXI LOVE RADIO (NAXI,Belgrade,Serbia, NAXI,Beograd,Srbija) - 128k

And in de async version:
D: ICY 200 OK
D: icy-notice1:
This stream requires Winamp

D: icy-notice2:SHOUTcast Distributed Network Audio Server/Linux
v1.9.8
icy-name:NAXI LOVE RADIO (NAXI,Belgrade,Serbia, NAXI,Beograd,Srbija) -
128k

So at least one linefeed is missing.
After that, the separation between mp3-data and metadata is not possible.

You will find the new source below.

Greetings,
Ed.


//******************************************************************************************
//* Esp_radio -- Webradio receiver for ESP8266, 1.8 color display and VS1053
MP3 module. *
//* With ESP8266 running at 80 MHz, it is capable of handling up to 256 kb
bitrate. *
//* With ESP8266 running at 160 MHz, it is capable of handling up to 320 kb
bitrate. *
//******************************************************************************************
// ESP8266 libraries used:
// - ESP8266WiFi
// - SPI
// - Adafruit_GFX
// - TFT_ILI9163C
// - EEPROM
// - ESPAsyncTCP
// - ESPAsyncWebServer
// - FS
// - ArduinoOTA
// A library for the VS1053 (for ESP8266) is not available (or not easy to
find). Therefore
// a class for this module is derived from the maniacbug library and integrated
in this sketch.
//
// See http://www.internet-radio.com for suitable stations. Add the stations of
your choice
// to the table "hostlist" in the global data secting further on. This will be
written to
// EEPROM and can be modified through remote access through the web server.
//
// Brief description of the program:
// First a suitable WiFi network is found and a connection is made.
// Then a connection will be made to a shoutcast server. The server starts with
some
// info in the header in readable ascii, ending with a double CRLF, like:
// icy-name:Classic Rock Florida - SHE Radio
// icy-genre:Classic Rock 60s 70s 80s Oldies Miami South Florida
// icy-url:http://www.ClassicRockFLorida.com
// content-type:audio/mpeg
// icy-pub:1
// icy-metaint:32768 - Metadata after 32768 bytes of MP3-data
// icy-br:128 - in kb/sec
//
// After de double CRLF is received, the server starts sending mp3-data. This
data contains
// metadata (non mp3) after every "metaint" mp3 bytes. This metadata is empty
in most cases,
// but if any is available the content will be presented on the TFT.
// Pushing the input button causes the player to select the next station in the
hostlist (EEPROM).
//
// The display used is a Chinese 1.8 color TFT module 128 x 160 pixels. The
TFT_ILI9163C.h
// file has been changed to reflect this particular module. TFT_ILI9163C.cpp
has been
// changed to use the full screenwidth if rotated to mode "3". Now there is
room for 26
// characters per line and 16 lines. Software will work without installing the
display.
//
// For configuration of the WiFi network(s): see the global data section further on.
//
// The SPI interface for VS1053 and TFT uses hardware SPI.
//
// Wiring:
// NodeMCU GPIO Pin to program Wired to LCD Wired to VS1053
Wired to rest

// ------- ------ -------------- --------------- -------------------

// D0 GPIO16 16 - pin 1 DCS -
// D1 GPIO5 5 - pin 2 CS LED
on nodeMCU
// D2 GPIO4 4 - pin 4 DREQ -
// D3 GPIO0 0 FLASH - -
Control button
// D4 GPIO2 2 pin 3 (D/C) - -
// D5 GPIO14 14 SCLK pin 5 (CLK) pin 5 SCK -
// D6 GPIO12 12 MISO - pin 7 MISO -
// D7 GPIO13 13 MOSI pin 4 (DIN) pin 6 MOSI -
// D8 GPIO15 15 pin 2 (CS) - -
// D9 GPI03 3 RXD0 - -
Reserved serial input
// D10 GPIO1 1 TXD0 - -
Reserved serial output

// ------- ------ -------------- --------------- -------------------

// GND - - pin 8 (GND) pin 8 GND
Power supply
// VCC 3.3 - - pin 6 (VCC) - LDO
3.3 Volt
// VCC 5 V - - - pin 9 5V
Power supply
// RST - - pin 1 (RST) pin 3 RESET
Reset circuit
//
// The reset circuit is a circuit with 2 diodes to GPIO5 and GPIO16 and a
resistor to ground
// (wired OR gate) because there was not a free GPIO output available for this
function.
// This circuit is included in the documentation.
// Issue:
// Webserver produces error "LmacRxBlk:1" after some time. After that it will
work verry slow.
// The program will reset the ESP8266 in such a case. It seems that this error
will show up
// less frequently if DNS is not used. Now we have switched to async webserver,
maybe that
// results in a better stability.
//
// 31-03-2016, ES: First set-up.
// 01-04-2016, ES: Detect missing VS1053 at start-up.
// 05-04-2016, ES: Added commands through http server on port 80.
// 06-04-2016, ES: Added list of stations in EEPROM
// 14-04-2016, ES: Added icon and switch preset on stream error.
// 18-04-2016, ES: Added SPIFFS for webserver
// 19-04-2016, ES: Added ringbuffer
// 20-04-2016, ES: WiFi Passwords through SPIFFS files, enable OTA
// 21-04-2016, ES: Switched to Async Webserver (thanks to me-no-dev)
// 26-04-2016, ES: Switched to Async client as well
//
#include <ESP8266mDNS.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESPAsyncTCP.h>
#include <SyncClient.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <TFT_ILI9163C.h>
#include <Ticker.h>
#include <stdio.h>
#include <string.h>
#include <EEPROM.h>
#include <FS.h>
#include <ArduinoOTA.h>

extern "C"
{
#include "user_interface.h"
}

// Color definitions for the TFT screen
#define BLACK 0x0000
#define BLUE 0xF800
#define RED 0x001F
#define GREEN 0x07E0
#define CYAN GREEN | BLUE
#define MAGENTA RED | BLUE
#define YELLOW RED | GREEN
#define WHITE 0xFFFF

// Digital I/O used
// Pins for VS1053 module
#define VS1053_CS 5
#define VS1053_DCS 16
#define VS1053_DREQ 4
// Pins CS and DC for TFT module
#define TFT_CS 15
#define TFT_DC 2
// Control button for controlling station
#define BUTTON 0
// Maximal number of presets in EEPROM and size of an entry
#define EENUM 64
#define EESIZ 64
// Ringbuffer for smooth playing 20000 bytes is 160 Kbits, about 1.5 seconds at
128kb bitrate.
#define RINGBFSIZ 20000

//******************************************************************************************
// Global data
section. *
//******************************************************************************************
enum datamode_t { INIT, HEADER, DATA, METADATA } ; // State for datastream
// Global variables
String ssid ; // SSID of selected
WiFi network
int DEBUG = 1 ;
#if defined ( dns )
MDNSResponder mdns ; // The MDNS responder
#endif
SyncClient mp3client ; // An instance of the
mp3 client
AsyncWebServer cmdserver ( 80 ) ; // Instance of
embedded webserver
String cmd ; // Command from remote
TFT_ILI9163C tft = TFT_ILI9163C ( TFT_CS, TFT_DC ) ;
Ticker tckr ; // For timing 10 sec
uint32_t totalcount = 0 ; // Counter mp3 data
char sbuf[100] ; // For debug lines
datamode_t datamode ; // State of datastream
int metacount ; // Number of bytes in
metadata
int datacount ; // Counter databytes
before metadata
char metaline[150] ; // Readable line in
metadata
char streamtitle[150] ; // Streamtitle from
metadata
int bitrate = 0 ; // Bitrate in
kb/sec
int metaint = 0 ; // Number of
databytes between metadata
int currentpreset = 1 ; // Preset station to
play (index in hostlist (EEPROM))
int newpreset = 1 ; // Requested preset
char host[EESIZ] ; // The hostname
char sname[100] ; // Stationname
int port ; // Port number for host
char newstation[EESIZ] ; // Station:port from
remote
int delpreset = 0 ; // Preset to be
deleted if nonzero
uint8_t reqvol = 80 ; // Requested volume
char currentstat[EESIZ] ; // Current station:port
uint8_t* ringbuf ; // Ringbuffer for VS1053
uint16_t rbwindex = 0 ; // Fill pointer in
ringbuffer
uint16_t rbrindex = RINGBFSIZ - 1 ; // Emptypointer in
ringbuffer
uint16_t rcount = 0 ; // Number of bytes in
ringbuffer
//
// List of initial preset stations.
// This will be copied to EEPROM if EEPROM is empty. The first entry [0] is
reserved
// for detection of a not yet filled EEPROM.
// In EEPROM, every entry takes EESIZ bytes.
const char* hostlist[] = {
"Stations from remote control", // Reserved entry
"109.206.96.34:8100", // 1 - NAXI LOVE RADIO,
Belgrade, Serbia 128-kbps
"us1.internet-radio.com:8180", // 2 - Easy Hits Florida
128-kbps
"us2.internet-radio.com:8050", // 3 - CLASSIC ROCK MIA
WWW.SHERADIO.COM
"us1.internet-radio.com:15919", // 4 - Magic Oldies Florida
"us2.internet-radio.com:8132", // 5 - Magic 60s Florida
60s Top 40 Classic Rock
"us1.internet-radio.com:8105", // 6 - Classic Rock
Florida - SHE Radio
"205.164.36.153:80", // 7 - BOM PSYTRANCE
(1.FM TM) 64-kbps
"205.164.62.15:10032", // 8 - 1.FM - GAIA, 64-kbps
"109.206.96.11:80", // 9 - TOP FM Beograd
106,8 64-kpbs
"85.17.121.216:8468", // 10 - RADIO LEHOVO 971
GREECE, 64-kbps
"85.17.121.103:8800", // 11 - STAR FM 88.8 Corfu
Greece, 64-kbps
"85.17.122.39:8530", // 12 - stylfm.gr laiko,
64-kbps
"94.23.66.155:8106", // 13 - ILR CHILL &
GROOVE
64-kbps
"205.164.62.22:7012", // 14 - 1.FM - ABSOLUTE
TRANCE (EURO) RADIO 64-kbps
NULL } ;

//******************************************************************************************
// End of lobal data
section. *
//******************************************************************************************

void dbgprint ( const char* p ) ; // Forward declaration for VS1053 stuff

//******************************************************************************************
// VS1053 stuff. Based on maniacbug
library. *
//******************************************************************************************
// VS1053 class
definition. *
//******************************************************************************************
class VS1053
{
private:
uint8_t cs_pin ; // Pin where CS line is connected
uint8_t dcs_pin ; // Pin where DCS line is connected
uint8_t dreq_pin ; // Pin where DREQ line is connected
uint8_t curvol ; // Current volume setting
0..100%
const uint8_t vs1053_chunk_size = 32 ;
// SCI Register
const uint8_t SCI_MODE = 0x0 ;
const uint8_t SCI_BASS = 0x2 ;
const uint8_t SCI_CLOCKF = 0x3 ;
const uint8_t SCI_AUDATA = 0x5 ;
const uint8_t SCI_WRAM = 0x6 ;
const uint8_t SCI_WRAMADDR = 0x7 ;
const uint8_t SCI_AIADDR = 0xA ;
const uint8_t SCI_VOL = 0xB ;
const uint8_t SCI_AICTRL0 = 0xC ;
const uint8_t SCI_AICTRL1 = 0xD ;
const uint8_t SCI_num_registers = 0xF ;
// SCI_MODE bits
const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on
const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset
const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song
const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests
const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for
Line input
SPISettings VS1053_SPI ; // SPI settings for this slave
uint8_t endFillByte ; // Byte to send when stopping song
char lsbuf[60] ; // For debugging
protected:
inline void await_data_request() const
{
while ( !digitalRead ( dreq_pin ) )
{
yield() ; // Very short delay
}
}

inline void control_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
digitalWrite ( dcs_pin, HIGH ) ; // Bring slave in dontrol mode
digitalWrite ( cs_pin, LOW ) ;
}

inline void control_mode_off() const
{
digitalWrite ( cs_pin, HIGH ) ; // End control mode
SPI.endTransaction() ; // Allow other SPI users
}

inline void data_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
digitalWrite ( cs_pin, HIGH ) ; // Bring slave in data mode
digitalWrite ( dcs_pin, LOW ) ;
}

inline void data_mode_off() const
{
digitalWrite ( dcs_pin, HIGH ) ; // End data mode
SPI.endTransaction() ; // Allow other SPI users
}

uint16_t read_register ( uint8_t _reg ) const ;
void write_register ( uint8_t _reg, uint16_t _value ) const ;
void sdi_send_buffer ( uint8_t* data, size_t len ) ;
void sdi_send_fillers ( size_t length ) ;
void wram_write ( uint16_t address, uint16_t data ) ;
uint16_t wram_read ( uint16_t address ) ;

public:
// Constructor. Only sets pin values. Doesn't touch the chip. Be sure to
call begin()!
VS1053 ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin ) ;
void begin() ; // Begin operation. Sets
pins correctly,
// and prepares SPI bus.
void startSong() ; // Prepare to start
playing. Call this each
// time a new song starts.
void playChunk ( uint8_t* data, size_t len ) ; // Play a chunk of data.
Copies the data to
// the chip. Blocks
until complete.
void stopSong() ; // Finish playing a song.
Call this after
// the last playChunk call.
void setVolume ( uint8_t vol ) ; // Set the player
volume.Level from 0-100,
// higher is louder.
uint8_t getVolume() ; // Get the currenet
volume setting.
// higher is louder.
void printDetails ( const char *header ) ; // Print configuration
details to serial output.
void softReset() ; // Do a soft reset
bool testComm ( const char *header ) ; // Test communication
with module
inline bool data_request() const
{
return ( digitalRead ( dreq_pin ) == HIGH ) ;
}
} ;

//******************************************************************************************
// VS1053 class
implementation. *
//******************************************************************************************

// Constructor
VS1053::VS1053 ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin ) :
cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin)
{
}

uint16_t VS1053::read_register ( uint8_t _reg ) const
{
uint16_t result ;

control_mode_on() ;
SPI.write ( 3 ) ; // Read operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
// Note: transfer16 does not seem to work
result = ( SPI.transfer ( 0xFF ) << 8 ) | // Read 16 bits data
( SPI.transfer ( 0xFF ) ) ;
await_data_request() ; // Wait for DREQ to be HIGH again
control_mode_off() ;
return result ;
}

void VS1053::write_register ( uint8_t _reg, uint16_t _value ) const
{
control_mode_on( );
SPI.write ( 2 ) ; // Write operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
SPI.write16 ( _value ) ; // Send 16 bits data
await_data_request() ;
control_mode_off() ;
}

void VS1053::sdi_send_buffer ( uint8_t* data, size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter

data_mode_on() ;
while ( len ) // More to do?
{
await_data_request() ; // Wait for space available
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
SPI.writeBytes ( data, chunk_length ) ;
data += chunk_length ;
}
data_mode_off() ;
}

void VS1053::sdi_send_fillers ( size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter

data_mode_on() ;
while ( len ) // More to do?
{
await_data_request() ; // Wait for space available
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
while ( chunk_length-- )
{
SPI.write ( endFillByte ) ;
}
}
data_mode_off();
}

void VS1053::wram_write ( uint16_t address, uint16_t data )
{
write_register ( SCI_WRAMADDR, address ) ;
write_register ( SCI_WRAM, data ) ;
}

uint16_t VS1053::wram_read ( uint16_t address )
{
write_register ( SCI_WRAMADDR, address ) ; // Start reading from WRAM
return read_register ( SCI_WRAM ) ; // Read back result
}

bool VS1053::testComm ( const char *header )
{
// Test the communication with the VS1053 module. The result wille be returned.
// If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH
// in order to prevent an endless loop waiting for this signal. The rest of the
// software will still work, but readbacks from VS1053 will fail.
int i ; // Loop control
uint16_t r1, r2, cnt = 0 ;
uint16_t delta = 300 ; // 3 for fast SPI

if ( !digitalRead ( dreq_pin ) )
{
dbgprint ( "VS1053 not properly installed!" ) ;
// Allow testing without the VS1053 module
pinMode ( dreq_pin, INPUT_PULLUP ) ; // DREQ is now input
with pull-up
return false ; // Return bad result
}
// Further TESTING. Check if SCI bus can write and read without errors.
// We will use the volume setting for this.
// Will give warnings on serial output if DEBUG is active.
// A maximum of 20 errors will be reported.
if ( strstr ( header, "Fast" ) )
{
delta = 3 ; // Fast SPI, more loops
}
dbgprint ( header ) ; // Show a header
for ( i = 0 ; ( i < 0xFFFF ) && ( cnt < 20 ) ; i += delta )
{
write_register ( SCI_VOL, i ) ; // Write data to SCI_VOL
r1 = read_register ( SCI_VOL ) ; // Read back for the
first time
r2 = read_register ( SCI_VOL ) ; // Read back a second time
if ( r1 != r2 || i != r1 || i != r2 ) // Check for 2 equal reads
{
sprintf ( lsbuf, "VS1053 error retry SB:%04X R1:%04X R2:%04X", i, r1, r2 ) ;
dbgprint ( lsbuf ) ;
cnt++ ;
delay ( 10 ) ;
}
yield() ; // Allow ESP firmware to
do some bookkeeping
}
return ( cnt == 0 ) ; // Return the result
}

void VS1053::begin()
{
pinMode ( dreq_pin, INPUT ) ; // DREQ is an input
pinMode ( cs_pin, OUTPUT ) ; // The SCI and SDI signals
pinMode ( dcs_pin, OUTPUT ) ;
digitalWrite ( dcs_pin, HIGH ) ; // Start HIGH for SCI en SDI
digitalWrite ( cs_pin, HIGH ) ;
delay ( 100 ) ;
dbgprint ( "Reset VS1053..." ) ;
digitalWrite ( dcs_pin, LOW ) ; // Low & Low will bring
reset pin low
digitalWrite ( cs_pin, LOW ) ;
delay ( 2000 ) ;
dbgprint ( "End reset VS1053..." ) ;
digitalWrite ( dcs_pin, HIGH ) ; // Back to normal again
digitalWrite ( cs_pin, HIGH ) ;
delay ( 500 ) ;
// Init SPI in slow mode ( 2 MHz )
VS1053_SPI = SPISettings ( 200000, MSBFIRST, SPI_MODE0 ) ;
//printDetails ( "Right after reset/startup" ) ;
delay ( 20 ) ;
//printDetails ( "20 msec after reset" ) ;
testComm ( "Slow SPI,Testing VS1053 read/write registers..." ) ;
// Most VS1053 modules will start up in midi mode. The result is that there
is no audio
// when playing MP3. You can modify the board, but there is a more elegant way:
wram_write ( 0xC017, 3 ) ; // Switch to MP3 mode
wram_write ( 0xC019, 0 ) ; // Switch to MP3 mode
delay ( 100 ) ;
//printDetails ( "After test loop" ) ;
softReset() ; // Do a soft reset
// Switch on the analog parts
write_register ( SCI_AUDATA, 44100 + 1 ) ; // 44.1kHz + stereo
// The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
write_register ( SCI_CLOCKF, 6 << 12 ) ; // Normal clock settings
multiplyer 3.0 = 12.2 MHz
//SPI Clock to 4 MHz. Now you can set high speed SPI clock.
VS1053_SPI = SPISettings ( 4000000, MSBFIRST, SPI_MODE0 ) ;
write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_LINE1 ) ) ;
testComm ( "Fast SPI, Testing VS1053 read/write registers again..." ) ;
delay ( 10 ) ;
await_data_request() ;
endFillByte = wram_read ( 0x1E06 ) & 0xFF ;
sprintf ( lsbuf, "endFillByte is %X", endFillByte ) ;
dbgprint ( lsbuf ) ;
//printDetails ( "After last clocksetting" ) ;
delay ( 100 ) ;
}

void VS1053::setVolume ( uint8_t vol )
{
// Set volume. Both left and right.
// Input value is 0..100. 100 is the loudest.
uint16_t value ; // Value to send to SCI_VOL

if ( vol != curvol )
{
curvol = vol ; // Save for later use
value = map ( vol, 0, 100, 0xFF, 0x00 ) ; // 0..100% to one channel
value = ( value << 8 ) | value ;
write_register ( SCI_VOL, value ) ; // Volume left and right
}
}

uint8_t VS1053::getVolume() // Get the currenet
volume setting.
{
return curvol ;
}

void VS1053::startSong()
{
sdi_send_fillers ( 10 ) ;
}

void VS1053::playChunk ( uint8_t* data, size_t len )
{
sdi_send_buffer ( data, len ) ;
}

void VS1053::stopSong()
{
uint16_t modereg ; // Read from mode register
int i ; // Loop control

sdi_send_fillers ( 2052 ) ;
delay ( 10 ) ;
write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_CANCEL ) ) ;
for ( i = 0 ; i < 200 ; i++ )
{
sdi_send_fillers ( 32 ) ;
modereg = read_register ( SCI_MODE ) ; // Read status
if ( ( modereg & _BV ( SM_CANCEL ) ) == 0 )
{
sdi_send_fillers ( 2052 ) ;
sprintf ( lsbuf, "Song stopped correctly after %d msec",
i * 10 ) ;
dbgprint ( lsbuf ) ;
return ;
}
delay ( 10 ) ;
}
printDetails ( "Song stopped incorrectly!" ) ;
}

void VS1053::softReset()
{
write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_RESET ) ) ;
delay ( 10 ) ;
await_data_request() ;
}

void VS1053::printDetails ( const char *header )
{
uint16_t regbuf[16] ;
uint8_t i ;

dbgprint ( header ) ;
dbgprint ( "REG Contents" ) ;
dbgprint ( "--- -----" ) ;
for ( i = 0 ; i <= SCI_num_registers ; i++ )
{
regbuf[i] = read_register ( i ) ;
}
for ( i = 0 ; i <= SCI_num_registers ; i++ )
{
delay ( 5 ) ;
sprintf ( lsbuf, "%3X - %5X", i, regbuf[i] ) ;
dbgprint ( lsbuf ) ;
}
}

// The object for the MP3 player
VS1053 mp3 ( VS1053_CS, VS1053_DCS, VS1053_DREQ ) ;

//******************************************************************************************
// End VS1053
stuff. *
//******************************************************************************************

//******************************************************************************************
// Ringbuffer (fifo)
routines. *
//******************************************************************************************
//******************************************************************************************
// R I N G S P A C
E *
//******************************************************************************************
inline bool ringspace()
{
return ( rcount < RINGBFSIZ ) ; // True is at least one byte of free space
is available
}

//******************************************************************************************
// R I N G A V A I
L *
//******************************************************************************************
inline uint16_t ringavail()
{
return rcount ; // Return number of bytes available
}

//******************************************************************************************
// P U T R I N
G *
//******************************************************************************************
bool putring ( uint8_t b ) // Put one byte in the ringbuffer
{
// No check on available space. See ringspace()
*(ringbuf+rbwindex) = b ; // Put byte in ringbuffer
if ( ++rbwindex == RINGBFSIZ ) // Increment pointer and
{
rbwindex = 0 ; // wrap at end
}
rcount++ ; // Count number of bytes in the
}

//******************************************************************************************
// G E T R I N
G *
//******************************************************************************************
uint8_t getring()
{
// Assume there is always something in the bufferpace. See ringavail()
if ( ++rbrindex == RINGBFSIZ ) // Increment pointer and
{
rbrindex = 0 ; // wrap at end
}
rcount-- ; // Count is now one less
return *(ringbuf+rbrindex) ; // return the oldest byte
}

//******************************************************************************************
// E M P T Y R I N
G *
//******************************************************************************************
void emptyring()
{
rbwindex = 0 ; // Reset ringbuffer administration
rbrindex = RINGBFSIZ - 1 ;
rcount = 0 ;
}

//******************************************************************************************
// D B G P R I N
T *
//******************************************************************************************
// Send a line of text to serial
output. *
//******************************************************************************************
void dbgprint ( const char* p )
{
if ( DEBUG )
{
Serial.print ( "D: " ) ;
Serial.println ( p ) ;
}
}

//******************************************************************************************
// G E T E N C R Y P T I O N T Y P
E *
//******************************************************************************************
// Read the encryption type of the network and return as a 4 byte
name *
//_4_***********************************************
const char* getEncryptionType ( int thisType )
{
switch (thisType)
{
case ENC_TYPE_WEP:
return "WEP " ;
case ENC_TYPE_TKIP:
return "WPA " ;
case ENC_TYPE_CCMP:
return "WPA2" ;
case ENC_TYPE_NONE:
return "None" ;
case ENC_TYPE_AUTO:
return "Auto" ;
}
return "????" ;
}

//******************************************************************************************
// L I S T N E T W O R K
S *
//******************************************************************************************
// List the available networks and select the
strongest. *
// Acceptable networks are those who have a "SSID.pw" file in the
SPIFFS. *
//******************************************************************************************
void listNetworks()
{
int maxsig = -1000 ; // Used for searching strongest WiFi signal
int newstrength ;
byte encryption ; // TKIP(WPA)=2, WEP=5, CCMP(WPA)=4, NONE=7, AUTO=8
const char* acceptable ; // Netwerk is acceptable for connection
int i, j ;
bool found ; // True if acceptable network found
String path ; // Full filespec to see if SSID is an
acceptable one

// scan for nearby networks:
dbgprint ( "* Scan Networks *" ) ;
int numSsid = WiFi.scanNetworks() ;
if ( numSsid == -1 )
{
dbgprint ( "Couldn't get a wifi connection" ) ;
return ;
}
// print the list of networks seen:
sprintf ( sbuf, "Number of available networks: %d",
numSsid ) ;
dbgprint ( sbuf ) ;

// Print the network number and name for each network found and
// find the strongest acceptable network
for ( i = 0 ; i < numSsid ; i++ )
{
acceptable = "" ; // Assume not acceptable
path = String ( "/" ) + WiFi.SSID ( i ) + String ( ".pw" ) ;
newstrength = WiFi.RSSI ( i ) ;
if ( found = SPIFFS.exists ( path ) ) // Is this SSID acceptable?
{
acceptable = "Acceptable" ;
if ( newstrength > maxsig ) // This is a better Wifi
{
maxsig = newstrength ;
ssid = WiFi.SSID ( i ) ; // Remember SSID name
}
}
encryption = WiFi.encryptionType ( i ) ;
sprintf ( sbuf, "%2d - %-25s Signal: %3d dBm Encryption %4s %s",
i + 1, WiFi.SSID ( i ).c_str(), WiFi.RSSI ( i ),
getEncryptionType ( encryption ),
acceptable ) ;
dbgprint ( sbuf ) ;
}
dbgprint ( "--------------------------------------" ) ;
sprintf ( sbuf, "Selected network: %-25s", ssid.c_str() ) ;
dbgprint ( sbuf ) ;
}

//******************************************************************************************
// T I M E R 1 0 S E
C *
//******************************************************************************************
// Extra watchdog. Called every 10
seconds. *
// If totalcount has not been changed, there is a problem and a reset will be
performed. *
//******************************************************************************************
void timer10sec()
{
static uint32_t oldtotalcount = 7321 ; // Needed foor change detection
static uint8_t morethanonce = 0 ; // Counter for succesive fails

if ( totalcount == oldtotalcount )
{
// No data detected!
dbgprint ( "No data input" ) ;
if ( morethanonce > 10 ) // Happened more than 10 times?
{
dbgprint ( "Going to restart..." ) ;
ESP.restart() ; // Reset the CPU, probably no return
}
if ( morethanonce >= 1 ) // Happened more than once?
{
newpreset++ ; // Yes, try next channel
dbgprint ( "Trying other station..." ) ;
}
morethanonce++ ; // Count the fails
}
else
{
if ( morethanonce ) // Recovered from data loss?
{
dbgprint ( "Recovered from dataloss" ) ;
morethanonce = 0 ; // Data see, reset failcounter
}
oldtotalcount = totalcount ; // Save for comparison in next cycle
}
}

//******************************************************************************************
// T I M E R 1 0
0 *
//******************************************************************************************
// Examine button every 100
msec. *
//******************************************************************************************
void timer100()
{
static int count10sec = 0 ;
static int oldval = HIGH ;
int newval ;

if ( ++count10sec == 100 ) // 10 seconds passed?
{
timer10sec() ; // Yes, do 10 second procedure
count10sec = 0 ; // Reset count
}
else
{
newval = digitalRead ( BUTTON ) ; // Test if below certain level
if ( newval != oldval )
{
oldval = newval ; // Remember value
if ( newval == LOW ) // Button pushed?
{
newpreset = currentpreset + 1 ; // Remember action
//dbgprint ( "Button pushed" ) ;
}
}
}
while ( mp3.data_request() && ringavail() ) // Try to keep VS1053 filled
{
handlebyte ( getring() ) ; // Yes, handle it
}
}

//******************************************************************************************
// D I S P L A Y I N F
O *
//******************************************************************************************
// Show a string on the LCD at a specified y-position in a specified
color *
//******************************************************************************************
void displayinfo ( const char *str, int pos, uint16_t color )
{
tft.fillRect ( 0, pos, 160, 40, BLACK ) ; // Clear the space for new info
tft.setTextColor ( color ) ; // Set the requested color
tft.setCursor ( 0, pos ) ; // Prepare to show the info
tft.print ( str ) ; // Show the string
}

//******************************************************************************************
// S H O W S T R E A M T I T L
E *
//******************************************************************************************
// show artist and songtitle if present in
metadata *
//******************************************************************************************
void showstreamtitle()
{
char* p1 ;
char* p2 ;

if ( strstr ( metaline, "StreamTitle=" ) )
{
p1 = metaline + 12 ; // Begin of artist and title
if ( p2 = strstr ( metaline, ";" ) ) // Search for end of title
{
if ( *p1 == ''' ) // Surrounded by quotes?
{
p1++ ;
p2-- ;
}
*p2 = '\0' ; // Strip the rest of the line
}
if ( *p1 == ' ' ) // Leading space?
{
p1++ ;
}
// Save last part of string as streamtitle. Protect against buffer overflow
strncpy ( streamtitle, p1, sizeof ( streamtitle ) ) ;
streamtitle[sizeof ( streamtitle ) - 1] = '\0' ;
}
else
{
return ; // Metadata does not contain streamtitle
}
if ( p1 = strstr ( streamtitle, " - " ) ) // look for artist/title separator
{
*p1++ = '\n' ; // Found: replace 3 characters by
newline
p2 = p1 + 2 ;
if ( *p2 == ' ' ) // Leading space in title?
{
p2++ ;
}
strcpy ( p1, p2 ) ; // Shift 2nd part of title 2 or 3 places
}
dbgprint ( metaline ) ;
displayinfo ( streamtitle, 20, CYAN ) ; // Show title at position 20
}

//******************************************************************************************
// C O N N E C T T O H O S
T *
//******************************************************************************************
// Connect to the Internet radio server specified by
newpreset. *
//******************************************************************************************
void connecttohost()
{
int i ; // Index free EEPROM entry
char* eepromentry ; // Pointer to copy of EEPROM
entry
char* p ; // Pointer in hostname

dbgprint ( "Connect to new host" ) ;
if ( mp3client.connected() )
{
dbgprint ( "Stop client" ) ; // Stop conection to host
///mp3client.flush() ;
mp3client.stop() ;
}
displayinfo ( " ** Internet radio **", 0, WHITE ) ;
if ( newstation[0] ) // New station specified by
host?
{
if ( strstr ( newstation, ":" ) ) // Correct format?
{
i = find_eeprom_station ( newstation ) ; // See if already in EEPROM
if ( i <= 0 )
{ // Not yet in EEPROM
i = find_free_eeprom_entry() ; // Find free EEPROM entry
(or entry 1)
put_eeprom_station ( i, newstation ) ; // Store new station
}
newpreset = i ; // Select this one
}
newstation[0] = '\0' ; // Handled this one
}
if ( newpreset < 1 || newpreset > EENUM ) // Requested preset within
limits?
{
newpreset = 1 ; // No, reset to the first preset
}
while ( true ) // Find entry in hostlist
that contains a colon.
{ // Will loop endlessly if
empty list
eepromentry = get_eeprom_station ( newpreset ) ;
strcpy ( currentstat, eepromentry ) ; // Save current station:port
if ( strstr ( eepromentry, ":" ) ) // Check format
{
break ; // Okay, leave loop
}
if ( ++newpreset == EENUM )
{
newpreset = 1 ; // Wrap around if beyond
highest preset
}
}
currentpreset = newpreset ; // This is the new preset
strcpy ( host, eepromentry ) ; // Select first station number
p = strstr ( host, ":" ) ; // Search for separator
*p++ = '\0' ; // Remove port from string
and point to port
port = atoi ( p ) ; // Get portnumber as integer
sprintf ( sbuf, "Connect to preset %d, host %s on port %d",
currentpreset, host, port ) ;
dbgprint ( sbuf ) ;
displayinfo ( sbuf, 60, YELLOW ) ; // Show info at position 60
delay ( 2000 ) ; // Show for some time
if ( mp3client.connect ( host, port ) )
{
dbgprint ( "Connected to server" ) ;
// This will send the request to the server. Request metadata.
mp3client.print ( String ( "GET / HTTP/1.1\r\n" ) +
"Host: " + host + "\r\n" +
"Icy-MetaData:1\r\n" +
"Connection: close\r\n\r\n") ;
}
datamode = INIT ; // Start in metamode
}

//******************************************************************************************
// F I N D _ F R E E _ E E P R O M _ E N T R
Y *
//******************************************************************************************
// Find a free EEPROM entry. If none is found: return entry

  1.                        *
    

//******************************************************************************************
int find_free_eeprom_entry()
{
int i ; // Entry number
char* p ; // Pointer to entry

for ( i = 1 ; i < EENUM ; i++ )
{
p = get_eeprom_station ( i ) ; // Get next entry
if ( *p == '\0' ) // Is this one empty?
{
return i ; // Yes, give index to caller
}
}
return 1 ; // No free entry, use the first
}

//******************************************************************************************
// G E T _ E E P R O M _ S T A T I O
N *
//******************************************************************************************
// Get a station from EEPROM. parameter index is
0..63. *
// A pointer to the station will be
returned. *
//******************************************************************************************
char* get_eeprom_station ( int index )
{
static char entry[EESIZ] ; // One station from EEPROM
int i ; // index in entry
int address ; // Address in EEPROM

address = index * EESIZ ; // Compute address in EEPROM
for ( i = 0 ; i < EESIZ ; i++ )
{
entry[i] = EEPROM.read ( address++ ) ;
yield() ;
}
return entry ; // Geef pointer terug
}

//******************************************************************************************
// P U T _ E E P R O M _ S T A T I O
N *
//******************************************************************************************
// Put a station into EEPROM. 1st parameter index is
0..63. *
//******************************************************************************************
void put_eeprom_station ( int index, const char *entry )
{
int i ; // index in entry
int address ; // Address in EEPROM

address = index * EESIZ ; // Compute address in EEPROM
for ( i = 0 ; i < EESIZ ; i++ )
{
EEPROM.write ( address++, entry[i] ) ;
}
yield() ;
EEPROM.commit() ; // Commit the write
}

//******************************************************************************************
// F I N D _ E E P R O M _ S T A T I O
N *
//******************************************************************************************
// Search for a station in EEPROM. Return the index or 0 if not
found. *
//******************************************************************************************
int find_eeprom_station ( const char search_entry )
{
char
p ; // Pointer to entry
int entnum ; // Entry number
int i ; // index in entry
int address ; // Address in EEPROM

for ( entnum = 1 ; entnum < EENUM ; entnum++ )
{
p = get_eeprom_station ( entnum ) ; // Get next entry
if ( strstr ( p, search_entry ) ) // Matches entry?
{
return entnum ; // Yes, return index
}
}
return 0 ; // No match found
}

//******************************************************************************************
// P U T _ E M P T Y _ E E P R O M _ S T A T I O
N *
//******************************************************************************************
// Put an empty station into EEPROM. parameter index is
1..63. *
//******************************************************************************************
void put_empty_eeprom_station ( int index )
{
int i ; // index in entry
int address ; // Address in EEPROM

address = index * EESIZ ; // Compute address in EEPROM
for ( i = 0 ; i < EENUM ; i++ )
{
EEPROM.write ( address++, 0 ) ;
}
yield() ;
EEPROM.commit() ; // Commit the write
}

//******************************************************************************************
// F I L L _ E E P R O
M *
//******************************************************************************************
// Setup EEPROM if
empty. *
//******************************************************************************************
void fill_eeprom()
{
int i ; // Entry number
int j ; // Index in hostlist
char *p ; // Pointer to copy of
EEPROM entry
int fillflag ; // Count of non-empty lines
in EEPROM

// See if first entry in EEPROM makes sense
p = get_eeprom_station ( 0 ) ; // Get reserved entry to
check status
if ( strcmp ( p, hostlist[0] ) == 0 ) // Starts with a familiar
pattern?
{
// Yes, show the list for debugging purposes
dbgprint ( "EEPROM is already filled. Available stations:" ) ;
for ( i = 0 ; i < EENUM ; i++ ) // List all for entries
{
p = get_eeprom_station ( i ) ; // Get next entry
if ( *p ) // Check if filled with a
station
{
fillflag = i ; // > 0 if at least one line
is filled
sprintf ( sbuf, "%02d - %s",
i, get_eeprom_station ( i ) ) ;
dbgprint ( sbuf ) ;
}
}
if ( fillflag )
{
return ; // EEPROM already filled
}
}
// EEPROM is virgin or empty. Fill it with default stations.
dbgprint ( "EEPROM is empty. Will be filled now." ) ;
delay ( 300 ) ;
j = 0 ; // Point to first line in
hostlist
for ( i = 0 ; i < EESIZ ; i++ ) // Space for all entries
{
if ( hostlist[j] ) // At last host in the list?
{
put_eeprom_station ( i, hostlist[j++] ) ; // Copy station
}
else
{
put_empty_eeprom_station ( i ) ; // Fill with a zero pattern
}
yield() ;
}
}

//******************************************************************************************
// C O N N E C T W I F
I *
//******************************************************************************************
// Connect to WiFi using passwords available in the
SPIFFS. *
// Returns false if no connection could be
made. *
//******************************************************************************************
bool connectwifi()
{
String path ; // Full file spec
String pw ; // Password from file
File pwfile ; // File containing
password for WiFi

path = String ( "/" ) + ssid + String ( ".pw" ) ; // Form full path
pwfile = SPIFFS.open ( path, "r" ) ; // File name equal to SSID
pw = pwfile.readStringUntil ( '\n' ) ; // Read password as a string
pw.trim() ; // Remove
CR
WiFi.begin ( ssid.c_str(), pw.c_str() ) ; // Connect to selected SSID
sprintf ( sbuf, "Try WiFi %s",
ssid.c_str() ) ; // Message to show during
WiFi connect
if ( WiFi.waitForConnectResult() != WL_CONNECTED )
{
dbgprint ( "WiFi connection failed" ) ; // Show failure
delay ( 2000 ) ;
return false ;
}
sprintf ( sbuf, "IP = %d.%d.%d.%d",
WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2],
WiFi.localIP()[3] ) ;
dbgprint ( sbuf ) ;
tft.println ( sbuf ) ;
return true ;
}

//******************************************************************************************
// S E T U
P *
//******************************************************************************************
// Setup for the
program. *
//******************************************************************************************
void setup()
{
FSInfo fs_info ;
byte mac[6] ;
int i ;

Serial.begin ( 115200 ) ; // For debug
Serial.println() ;
system_update_cpu_freq ( 160 ) ; // Set to 80/160 MHz
ringbuf = (uint8_t ) malloc ( RINGBFSIZ ) ; // Create ring buffer
SPIFFS.begin() ; // Enable file system
// Show some info about the SPIFFS
SPIFFS.info ( fs_info ) ;
sprintf ( sbuf, "FS Total %d, used %d", fs_info.totalBytes, fs_info.usedBytes ) ;
dbgprint ( sbuf ) ;
Dir dir = SPIFFS.openDir("/") ; // Show files in FS
while ( dir.next() ) // All files
{
File f = dir.openFile ( "r" ) ;
String filename = dir.fileName() ;
sprintf ( sbuf, "%-32s - %6d", // Show name and size
filename.c_str(), f.size() ) ;
dbgprint ( sbuf ) ;
}
WiFi.mode ( WIFI_STA ) ; // This ESP is a station
wifi_station_set_hostname ( (char
)"ESP-radio" ) ;
SPI.begin() ; // Init SPI bus
EEPROM.begin ( 2048 ) ; // For station list in EEPROM
sprintf ( sbuf, // Some memory info
"Starting ESP V2.1... Free memory %d",
system_get_free_heap_size() ) ;
dbgprint ( sbuf ) ;
sprintf ( sbuf, // Some sketch info
"Sketch size %d, free size %d",
ESP.getSketchSize(),
ESP.getFreeSketchSpace() ) ;
dbgprint ( sbuf ) ;
fill_eeprom() ; // Fill if empty
pinMode ( BUTTON, INPUT ) ; // Input for control button
mp3.begin() ; // Initialize VS1053 player
tft.begin() ; // Init TFT interface
tft.fillRect ( 0, 0, 160, 128, BLACK ) ; // Clear screen does not
work when rotated
tft.setRotation ( 3 ) ; // Use landscape format
tft.clearScreen() ; // Clear screen
tft.setTextSize ( 1 ) ; // Small character font
tft.setTextColor ( WHITE ) ;
tft.println ( "Starting" ) ;
delay(10);
streamtitle[0] = '\0' ; // No title yet
newstation[0] = '\0' ; // No new station yet
tckr.attach ( 0.100, timer100 ) ; // Every 100 msec
listNetworks() ; // Search for strongest
WiFi network
if ( !connectwifi() ) // Connect to WiFi network
{
ESP.restart() ; // No success
}
#if defined ( dns )
dbgprint ( "Start MDNS responder" ) ;
if ( !mdns.begin ( "ESP-radio",
WiFi.localIP() ) ) // Start DNS responder
{
dbgprint ( "Error setting up MDNS responder!" ) ;
}
#endif
dbgprint ( "Start server for commands" ) ;
// Specify handling of the various commands (case sensitive). The startpage
will be returned if
// no arguments are given. The first argument has the format with
"/?parameter=value".
// Example: "/?volume=90"
// Multiple commands are alowed, like "/?volume=95&station=+1"
cmdserver.on ( "/", handleCmd ) ; // Handle startpage
cmdserver.onNotFound ( handleFS ) ; // Handle file from FS
cmdserver.begin() ;
delay ( 1000 ) ; // Show IP for a wile
connecttohost() ; // Connect to the selected host
ArduinoOTA.begin() ; // Allow update over the air
}

//******************************************************************************************
// L O O
P *
//******************************************************************************************
// Main loop of the program. Minimal time is 20 usec. Will take about 4 msec
if VS1053 *
// needs
data. *
// Sometimes the loop is called after an interval of more than 100
msec. *
// In that case we will not be able to fill the internal VS1053-fifo in time
(especially *
// at high
bitrate). *
// A connection to an MP3 server is active and we are ready to receive
data. *
// Normally there is about 2 to 4 kB available in the data stream. This depends
on the *
//
sender.
*
//******************************************************************************************
void loop()
{
// Try to keep the ringbuffer filled up by adding as much bytes as possible
while ( ringspace() && mp3client.available() )
{
putring ( mp3client.read() ) ; // Yes, save one byte in
ringbuffer
}
yield() ;
while ( mp3.data_request() && ringavail() ) // Try to keep VS1053 filled
{
handlebyte ( getring() ) ; // Yes, handle it
}
if ( delpreset ) // Delete preset requested?
{
put_empty_eeprom_station ( delpreset ) ; // Fill with a zero pattern
if ( delpreset == currentpreset ) // Listening to a deleted station?
{
newpreset++ ; // Yes, select the next preset
}
delpreset = 0 ; // Just once
}
if ( newpreset != currentpreset ) // New station requested?
{
mp3.setVolume ( 0 ) ; // Mute
mp3.stopSong() ; // Stop playing
emptyring() ; // Empty the ringbuffer
connecttohost() ; // Switch to new host
}
mp3.setVolume ( reqvol ) ; // Set to requested volume
ArduinoOTA.handle() ; // Check for OTA
}

//******************************************************************************************
// H A N D L E B Y T
E *
//******************************************************************************************
// Handle the next byte of data from
server. *
// This byte will be send to the VS1053 most of the
time. *
// Note that the buffer the data chunk must start at an address that is a
muttiple of 4. *
//******************************************************************************************
void handlebyte ( uint8_t b )
{
static int metaindex ; // Index in metaline
static bool firstmetabyte ; // True if first
metabyte (counter)
static int LFcount ; // Detection of end of
header
static attribute((aligned(4))) uint8_t buf[32] ; // Buffer for chunk
static int chunkcount = 0 ; // Data in chunk
static bool firstchunk = true ; // First chunk as input
char* p ; // Pointer in metaline
int i ; // Loop control

switch ( datamode )
{
case DATA : // Handle next byte of
MP3 data
buf[chunkcount++] = b ; // Save byte in cunkbuffer
if ( chunkcount == sizeof(buf) ) // Buffer full?
{
if ( firstchunk )
{
firstchunk = false ;
dbgprint ( "First chunk:" ) ; // Header for printout of
first chunk
for ( i = 0 ; i < 32 ; i += 8 ) // Print 4 lines
{
sprintf ( sbuf, "%02X %02X %02X %02X %02X %02X %02X %02X",
buf[i], buf[i+1], buf[i+2], buf[i+3],
buf[i+4], buf[i+5], buf[i+6], buf[i+7] ) ;
dbgprint ( sbuf ) ;
}
}
mp3.playChunk ( buf, chunkcount ) ; // Yes, send to player
chunkcount = 0 ; // Reset count
}
totalcount++ ; // Count number of bytes,
ignore overflow
if ( --datacount == 0 ) // End of datablock?
{
if ( chunkcount ) // Yes, stil data in buffer?
{
mp3.playChunk ( buf, chunkcount ) ; // Yes, send to player
chunkcount = 0 ; // Reset count
}
datamode = METADATA ;
firstmetabyte = true ; // Expecting first
metabyte (counter)
}
break ;
case INIT : // Initialize for header
receive
LFcount = 0 ; // For detection end of
header
bitrate = 0 ; // Bitrate still unknown
metaindex = 0 ; // Prepare for new line
datamode = HEADER ; // Handle header
totalcount = 0 ; // Reset totalcount
// slip into HEADER handling, no break!
case HEADER : // Handle next byte of
header
if ( ( b > 0x7F ) || // Ignore unprintable
characters
( b == '\r' ) || // Ignore CR
( b == '\0' ) ) // Ignore NULL
{
// Yes, ignore
}
else if ( b == '\n' ) // Linefeed ?
{
LFcount++ ; // Count linefeeds
metaline[metaindex] = '\0' ; // Mark end of string
metaindex = 0 ; // Reset for next line
dbgprint ( metaline ) ; // Show it
if ( p = strstr ( metaline, "icy-br:" ) )
{
// Found bitrate tag, read the bitrate
bitrate = atoi ( p + 7 ) ;
dbgprint ( "Bitrate set" ) ;
}
else if ( p = strstr ( metaline, "icy-metaint:" ) )
{
// Found bitrate tag, read the bitrate
metaint = atoi ( p + 12 ) ;
dbgprint ( "Metaint set" ) ;
}
else if ( p = strstr ( metaline, "icy-name:" ) )
{
// Found station name, save it, prevent overflow
strncpy ( sname, p + 9, sizeof ( sname ) ) ;
sname[sizeof(sname)-1] = '\0' ;
displayinfo ( sname, 60, YELLOW ) ; // Show title at position 60
dbgprint ( "Name set" ) ;
}
if ( bitrate && ( LFcount == 2 ) )
{
datamode = DATA ; // Expecting data
datacount = metaint ; // Number of bytes before
first metadata
chunkcount = 0 ; // Reset chunkcount
dbgprint ( "2 LINEFEEDs seen" ) ;
mp3.startSong() ; // Start a new song
}
}
else
{
metaline[metaindex] = (char)b ; // Normal character, put
new char in metaline
if ( metaindex < ( sizeof(metaline) - 2 ) ) // Prevent buffer overflow
{
metaindex++ ;
}
LFcount = 0 ; // Reset double CRLF detection
}
break ;
case METADATA : // Handle next bye of metadata
if ( firstmetabyte ) // First byte of metadata?
{
firstmetabyte = false ; // Not the first anymore
metacount = b * 16 + 1 ; // New count for metadata
including length byte
metaindex = 0 ; // Place to store metadata
if ( metacount > 1 )
{
sprintf ( sbuf, "Metadata block %d bytes",
metacount-1 ) ; // Most of the time there
are zero bytes of metadata
dbgprint ( sbuf ) ;
}
}
else
{
metaline[metaindex] = (char)b ; // Normal character, put
new char in metaline
if ( metaindex < ( sizeof(metaline) - 2 ) ) // Prevent buffer overflow
{
metaindex++ ;
}
}
if ( --metacount == 0 )
{
if ( metaindex ) // Any info present?
{
metaline[metaindex] = '\0' ;
// metaline contains artist and song name. For example:
// "StreamTitle='Don McLean - American Pie';StreamUrl='';"
// Sometimes it is just other info like:
// "StreamTitle='60s 03 05 Magic60s';StreamUrl='';"
// Isolate the StreamTitle, remove leading and trailing quotes if present.
showstreamtitle() ; // Show artist and title
if present in metadata
}
datacount = metaint ; // Reset data count
chunkcount = 0 ; // Reset chunkcount
datamode = DATA ; // Expecting data
}
break ;
}
}

//******************************************************************************************
//

@me-no-dev
Copy link
Author

Somehow I can not see the source, but I was able to get the sketch running here and listen to some music, so I'll give it a go also. Bytes should not be missing

@Edzelf
Copy link
Owner

Edzelf commented Apr 26, 2016

Here is the source.
Esp_radio_async.zip

@me-no-dev
Copy link
Author

really interesting! I tried different stations and it's always that byte that is missing? always before icy-name... looking deeper... btw I meant to use AsyncClient instead of the Sync one, but I'll look into that too.

@Edzelf
Copy link
Owner

Edzelf commented Apr 26, 2016

Sorry, it was the wrong source indeed. Her is the right one (I hope).
Esp_radio_async.zip

@me-no-dev
Copy link
Author

this is nice :) i finally found a problem with assimilating data. Onto fixing it...
Issue is that data is acked too early and the heap get's emptied quickly

@me-no-dev
Copy link
Author

delaying Acks fixed SyncClient (result is in latest git). What it showed though is that the ESP will not be able to handle the data asynchronously, because the rate of incoming packets and rate of consumption differ much. On another note, I totally removed the 20K buffer and the streams are playing fine (since you fill the buffer and right after that you read it, you can just skip it and use the buffers that the clients have).
Performance of SyncClient vs WiFiClient will not differ, and to be honest, in your exact case, you are better off using the regular WiFiClient to buffer the stream, because it uses the TCP layer's own packet buffers and saves a bit of transactions with the memory that way.
I saw you are trying to start MDNS. MDNS is now a global service and is started by ArduinoOTA.begin() or just MDNS.begin(hostname). to change the default hostname for MDNS you can call ArduinoOTA.setHostname("ESP-radio"); before ArduinoOTA.begin();

@Edzelf
Copy link
Owner

Edzelf commented Apr 27, 2016

Okay. I will stick to the regular client.
I introduced the circular buffer for better performance on high bitrates only.
Lower bitrates will work without the ringbuffer.

Op 27-04-16 om 01:14 schreef Me No Dev:

delaying Acks fixed SyncClient (result is in latest git). What it showed
though is that the ESP will not be able to handle the data asynchronously,
because the rate of incoming packets and rate of consumption differ much. On
another note, I totally removed the 20K buffer and the streams are playing
fine (since you fill the buffer and right after that you read it, you can just
skip it and use the buffers that the clients have).
Performance of SyncClient vs WiFiClient will not differ, and to be honest, in
your exact case, you are better off using the regular WiFiClient to buffer the
stream, because it uses the TCP layer's own packet buffers and saves a bit of
transactions with the memory that way.
I saw you are trying to start MDNS. MDNS is now a global service and is
started by |ArduinoOTA.begin()| or just |MDNS.begin(hostname)|. to change the
default hostname for MDNS you can call |ArduinoOTA.setHostname("ESP-radio");|
before |ArduinoOTA.begin();|


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#2 (comment)

@danbicks
Copy link

Ed,

Awesome work, have one already knocked up and playing my fav stations, have you thought about adding the parametic EQ functions for bass and treble?

Great work mate, well done

Dans

@Edzelf
Copy link
Owner

Edzelf commented Apr 30, 2016

Thanks for kind words.  In the sourcecode you will see a remark on bass and treble as a "todo".  The next version will also save settings (preset and volume) over a restart.
Ed.

Verzonden vanaf Samsung-tablet.

-------- Oorspronkelijk bericht --------
Van: danbicks notifications@github.com
Datum: 30-04-2016 18:22 (GMT+01:00)
Aan: Edzelf/Esp-radio Esp-radio@noreply.github.com
Cc: Ed Smallenburg ed@smallenburg.nl, Comment comment@noreply.github.com
Onderwerp: [PossibleSpam] Re: [Edzelf/Esp-radio] Why not move mp3client to AsyncTCP as well?
(#2)
(#2)

Ed,

Awesome work, have one already knocked up and playing my fav stations, have you thought about adding the parametic EQ functions for bass and treble?

Great work mate, well done

Dans


You are receiving this because you commented.
Reply to this email directly or view it on GitHub

@danbicks
Copy link

Fantastic, roll on version 2 Woooo

Cheers mate

Dans

@danbicks
Copy link

danbicks commented May 2, 2016

Hi @Edzelf ,

Do you have some sample routines to increase / decrease bass and treble parameters?

I now have added a simple 5 button push button panel reading from the analogue input pin, allowing me based on the 100 ms timer to increment / decrement preset, I would like to use the other buttons for EQ bass and treble for now.

Big thanks buddy and so impressed with the quality of the streams I have added :)

Cheers

Dans

@Edzelf
Copy link
Owner

Edzelf commented May 3, 2016

The new published version has bass/treble control. Don't forget to upload the index.html as well.

@danbicks
Copy link

danbicks commented May 3, 2016

Ed, you are simply a Guru, just got in now really excited to see how this has been implemented.

I was racking through the datasheet last night and came up with a superb idea, I am sure you may have thought about this. What about turning this the other way around and making an ESP-MP3-Encoder?

The board I have is the same as yours, the idea would be to reverse your operation and to stream the MP3 microphone encoded to a version of the radio code to pick up the stream and in effect create a point to point audio link. What do you see as the main complexity's involved with this?

Adafuit have implemented a ogg example recording to SD card, the initialization is pretty simple although will require a plugin for the vs1053 which is available. This would be seriously cool and allow for high quality point to point streaming.

Well done mate, keep up the awesome work.

Dans

@Edzelf
Copy link
Owner

Edzelf commented May 4, 2016

Nice idea. Technically it is interesting. The sender can act as an accesspoint, that makes it portable. A complications might be the synchronization: what happens if the sender and receiver differ only slightly in bps-speed... The coding have to be ogg, because the VS1053 cannot encode MP3.
I will do some experimenting with encoding, but personally I have no usefull application for point to point streaming.

@danbicks
Copy link

danbicks commented May 4, 2016

Hi Ed,

I think it would be a really cool project. Thinking about operation, the
client "sender" attempts connection to the server basically the mp3 decoder
running ogg. Basic socket connection and then some form of simple
authentication, I.E chip id is sent from client, server checks validates
and then sends a command back to client to start sending the stream, maybe
this approach would allow synchronization to take place, I don't know a lot
about ogg format and will be researching this now. Would be superb if you
get a chance to tinker with the encoding and send stream side. I think this
system would only work point to point due to ESP limitation in remote
connections, but would be really cool concept.

Glad you are interested in this, what other stuff do you get up to in your
day job?

Big thanks

Dans

On Wed, May 4, 2016 at 11:15 AM, Ed Smallenburg notifications@github.com
wrote:

Nice idea. Technically it is interesting. The sender can act as an
accesspoint, that makes it portable. A complications might be the
synchronization: what happens if the sender and receiver differ only
slightly in bps-speed... The coding have to be ogg, because the VS1053
cannot encode MP3.
I will do some experimenting with encoding, but personally I have no
usefull application for point to point streaming.


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#2 (comment)

@Edzelf
Copy link
Owner

Edzelf commented May 4, 2016

I 'll see what I can do. It will take some time...

@danbicks
Copy link

danbicks commented May 5, 2016

Thanks buddy,

This will be really interesting.

Good luck, keep me posted

Dans

On Wed, May 4, 2016 at 8:04 PM, Ed Smallenburg notifications@github.com
wrote:

I 'll see what I can do. It will take some time...


You are receiving this because you commented.
Reply to this email directly or view it on GitHub
#2 (comment)

@Edzelf
Copy link
Owner

Edzelf commented May 11, 2016

@danbicks
I have a first version of an Ogg-encoder ready for you. See the zip-file.

@danbicks
Copy link

Ed,

Wow so excited, well done you, had a look through the code, awesome mate. Just ordered another VS1053 board to try. I think for an interim I can use winamp to connect and test, will give this a go in the short term.

Any major issue's you encountered while putting this together? how did you test it and where you impressed with audio results?

Superb work, cheers Guru

Dans

@Edzelf
Copy link
Owner

Edzelf commented May 11, 2016

Dans,
It was not that difficult to cut and paste things together. Major problem was (again) the fact that GPIO[0] and GPIO[1] are floating on my cheap Chinese board. Then the conversion stops right after generating the Ogg header (699 words), see http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=7&t=595&sid=b902cfa400af926.
Another problem is that sometimes the initialization of the board fails. After one or 2 resets it starts suddenly and it will work for hours. I will try to find out what the problem is.

I tested it with Firefox, and the quality (microphone input) was good. I noticed a delay of 2.5 seconds.
I tried Winamp, but no success.

Have not tried the Esp-radio yet, but it should work.

Here some logging under normal operations:
D: IP = 192.168.2.13
D: Reset VS1053...
D: End reset VS1053...
D: Slow SPI,Testing VS1053 read/write registers...
D: Fast SPI, Testing VS1053 read/write registers again...
D: Loading Ogg plugin
D: Result of load is 0x34
D: Waiting for connection...
pm open,type:2 0
D: New client connect
D: Starting conversion
D: Conversion started
GET / HTTP/1.1
Host: 192.168.2.13:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: nl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
D: 52970 bytes converted
D: 156678 bytes converted
D: 260922 bytes converted
D: 364848 bytes converted
D: 468096 bytes converted
D: 564968 bytes converted
D: 669830 bytes converted
D: 773106 bytes converted
D: 876926 bytes converted
D: 982852 bytes converted

@danbicks
Copy link

Ed,

Amazing work, I have just downloaded Firefox and yes all connected up fine
same view as yours connection alive, I am using the onboard microphone but
no sound is coming through, did you use an external mic or line input?

Those chinese cheap boards are a pain, I have this knocked up on a NODEMCU,
I have changed original pins 4 and 5 and swapped to GPIO 0 and 2 as using
I2c. works perfect on ESP radio, not sure if this is causing the no sound
issue on ogg encoder?

I will keep tinkering, A plus mate

Dans

On Wed, May 11, 2016 at 7:15 PM, Ed Smallenburg notifications@github.com
wrote:

Dans,
It was not that difficult to cut and paste things together. Major problem
was (again) the fact that GPIO[0] and GPIO[1] are floating on my cheap
Chinese board. Then the conversion stops right after generating the Ogg
header (699 words), see
http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=7&t=595&sid=b902cfa400af926
.
Another problem is that sometimes the initialization of the board fails.
After one or 2 resets it starts suddenly and it will work for hours. I will
try to find out what the problem is.

I tested it with Firefox, and the quality (microphone input) was good. I
noticed a delay of 2.5 seconds.
I tried Winamp, but no success.

Have not tried the Esp-radio yet, but it should work.

Here some logging under normal operations:
D: IP = 192.168.2.13
D: Reset VS1053...
D: End reset VS1053...
D: Slow SPI,Testing VS1053 read/write registers...
D: Fast SPI, Testing VS1053 read/write registers again...
D: Loading Ogg plugin
D: Result of load is 0x34
D: Waiting for connection...
pm open,type:2 0
D: New client connect
D: Starting conversion
D: Conversion started
GET / HTTP/1.1
Host: 192.168.2.13:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101
Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: nl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
D: 52970 bytes converted
D: 156678 bytes converted
D: 260922 bytes converted
D: 364848 bytes converted
D: 468096 bytes converted
D: 564968 bytes converted
D: 669830 bytes converted
D: 773106 bytes converted
D: 876926 bytes converted
D: 982852 bytes converted


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#2 (comment)

@Edzelf
Copy link
Owner

Edzelf commented May 11, 2016

Dans,
I used the  microphone.  During conversion, you can monitor the sound through the headphone connection.

@Edzelf
Copy link
Owner

Edzelf commented May 12, 2016

I made some changes to Esp-radio and Ogg-encoder and now the radio can receive data from the encoder. Issue: the radio reads a bit faster than the encoder produces. So the ringbuffer is always empty. I replaced boths sketches to http://smallenburg.nl/Ogg-encoder.zip. You can also find my mailaddress on the website, so you can mail me directly.

@danbicks
Copy link

Ed,

Awesome work buddy, sorry for delay had to re build all data on to new laptop, my old one suddenly decided to type when no keys pressed lol. Too much programming for it Haha.

Super stuff buddy, I did try the last encoder and found that although mozilla locked on to the stream no audio would come through, I did plug in to the headphone socket and audio was their from the microphone strange. I will get some time hopefully next week to follow this up, finishing off my garden lighting system since the weather is really nice, features 5 outputs all PWM controlled by android app on my phone, I will send over some pics when up and running.

Keep up the amazing work.

Dans

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants