├── .github └── FUNDING.yml ├── .gitignore ├── ArduinoJson-v6.15.1.h ├── AudioCustomLogger.cpp ├── AudioCustomLogger.h ├── AudioFileSourceFatFs.cpp ├── AudioFileSourceFatFs.h ├── AudioGeneratorTonie.cpp ├── AudioGeneratorTonie.h ├── AudioGeneratorWAVStatic.cpp ├── AudioGeneratorWAVStatic.h ├── AudioOutputCC3200I2S.cpp ├── AudioOutputCC3200I2S.h ├── AudioOutputResample.cpp ├── AudioOutputResample.h ├── BaseHeader.h ├── BoxAccelerometer.cpp ├── BoxAccelerometer.h ├── BoxAudioBuffer.cpp ├── BoxAudioBuffer.h ├── BoxAudioBufferTriple.cpp ├── BoxAudioBufferTriple.h ├── BoxBattery.cpp ├── BoxBattery.h ├── BoxButtonEars.cpp ├── BoxButtonEars.h ├── BoxCLI.cpp ├── BoxCLI.h ├── BoxConfig.cpp ├── BoxConfig.h ├── BoxDAC.cpp ├── BoxDAC.h ├── BoxEvents.cpp ├── BoxEvents.h ├── BoxI2C.cpp ├── BoxI2C.h ├── BoxLEDs.cpp ├── BoxLEDs.h ├── BoxPlayer.cpp ├── BoxPlayer.h ├── BoxPower.cpp ├── BoxPower.h ├── BoxRFID.cpp ├── BoxRFID.h ├── BoxSD.cpp ├── BoxSD.h ├── BoxTimer.cpp ├── BoxTimer.h ├── BoxTonies.cpp ├── BoxTonies.h ├── ConfigStatic.h.example ├── ConfigStructures.h ├── Hackiebox.cpp ├── Hackiebox.h ├── LogStreamMulti.cpp ├── LogStreamMulti.h ├── LogStreamSd.cpp ├── LogStreamSd.h ├── LogStreamSse.cpp ├── LogStreamSse.h ├── README.md ├── TonieStructures.h ├── WrapperWebServer.cpp ├── WrapperWebServer.h ├── WrapperWiFi.cpp ├── WrapperWiFi.h ├── audio ├── both-of-us-14037.wav ├── cinematic-fairy-tale-story-main-8697.wav ├── electronic-rock-king-around-here-15045.wav ├── into-the-night-20928.wav ├── melody-of-nature-main-6672.wav ├── nightlife-michael-kobrin-95bpm-3783.wav ├── source │ ├── both-of-us-14037.txt │ ├── cinematic-fairy-tale-story-main-8697.txt │ ├── electronic-rock-king-around-here-15045.txt │ ├── into-the-night-20928.txt │ ├── melody-of-nature-main-6672.txt │ ├── nightlife-michael-kobrin-95bpm-3783.txt │ ├── the-cradle-of-your-soul-15700.txt │ ├── trailer-sport-stylish-16073.txt │ └── yesterday-extended-version-14197.txt ├── the-cradle-of-your-soul-15700.wav ├── trailer-sport-stylish-16073.wav └── yesterday-extended-version-14197.wav ├── hackiebox_cfw.ino └── web ├── hackiebox.html └── local-echo.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: SciLor # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ConfigStatic.h 2 | .vscode/* 3 | build/out/* 4 | -------------------------------------------------------------------------------- /AudioCustomLogger.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "AudioCustomLogger.h" 3 | -------------------------------------------------------------------------------- /AudioCustomLogger.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #ifndef _AUDIOCUSTOMLOGGER_H 8 | #define _AUDIOCUSTOMLOGGER_H 9 | 10 | class AudioCustomLogger: public DevNullOut 11 | { 12 | public: 13 | virtual size_t write(uint8_t c) { 14 | Log.printf("%c", c); 15 | return 1; 16 | }; 17 | int printf_P(const char *msg, ...) { 18 | va_list args; 19 | va_start(args, msg); 20 | Log.printFormat(msg, args); 21 | return 0; //TOOD? 22 | }; 23 | }; 24 | 25 | #endif -------------------------------------------------------------------------------- /AudioFileSourceFatFs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AudioFileSourceSPIFFS 3 | Input SD card "file" to be used by AudioGenerator 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "AudioFileSourceFatFs.h" 22 | 23 | AudioFileSourceFatFs::AudioFileSourceFatFs() 24 | { 25 | _isOpen = false; 26 | } 27 | 28 | AudioFileSourceFatFs::AudioFileSourceFatFs(const char *filename) 29 | { 30 | _isOpen = false; 31 | open(filename); 32 | } 33 | 34 | bool AudioFileSourceFatFs::open(const char *filename) 35 | { 36 | if (_isOpen) file.close(); 37 | _isOpen = file.open((char*)filename, FA_OPEN_EXISTING | FA_READ); 38 | return _isOpen; 39 | } 40 | 41 | AudioFileSourceFatFs::~AudioFileSourceFatFs() 42 | { 43 | if (_isOpen) file.close(); 44 | } 45 | 46 | uint32_t AudioFileSourceFatFs::read(void *data, uint32_t len) 47 | { 48 | return file.read(data, len); 49 | } 50 | 51 | bool AudioFileSourceFatFs::seek(int32_t pos, int dir) 52 | { 53 | if (!_isOpen) return false; 54 | if (dir==SEEK_SET) return file.seekSet(pos); 55 | else if (dir==SEEK_CUR) return file.seekSet(file.curPosition() + pos); 56 | else if (dir==SEEK_END) return file.seekSet(file.fileSize() + pos); 57 | return false; 58 | } 59 | 60 | bool AudioFileSourceFatFs::close() 61 | { 62 | _isOpen = false; 63 | return file.close(); 64 | } 65 | 66 | bool AudioFileSourceFatFs::isOpen() 67 | { 68 | return _isOpen; 69 | } 70 | 71 | uint32_t AudioFileSourceFatFs::getSize() 72 | { 73 | if (!_isOpen) return 0; 74 | return file.fileSize(); 75 | } 76 | 77 | uint32_t AudioFileSourceFatFs::getPos() 78 | { 79 | if (!_isOpen) return 0; 80 | return file.curPosition(); 81 | } 82 | -------------------------------------------------------------------------------- /AudioFileSourceFatFs.h: -------------------------------------------------------------------------------- 1 | /* 2 | AudioFileSourceSPIFFS 3 | Input SD card "file" to be used by AudioGenerator 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _AUDIOFILESOURCEFATFS_H 22 | #define _AUDIOFILESOURCEFATFS_H 23 | 24 | #include "AudioFileSource.h" 25 | #include 26 | 27 | class AudioFileSourceFatFs : public AudioFileSource 28 | { 29 | public: 30 | AudioFileSourceFatFs(); 31 | AudioFileSourceFatFs(const char *filename); 32 | virtual ~AudioFileSourceFatFs() override; 33 | 34 | virtual bool open(const char *filename) override; 35 | virtual uint32_t read(void *data, uint32_t len) override; 36 | virtual bool seek(int32_t pos, int dir) override; 37 | virtual bool close() override; 38 | virtual bool isOpen() override; 39 | virtual uint32_t getSize() override; 40 | virtual uint32_t getPos() override; 41 | 42 | private: 43 | FileFs file; 44 | bool _isOpen; 45 | }; 46 | 47 | 48 | #endif 49 | 50 | -------------------------------------------------------------------------------- /AudioGeneratorTonie.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AudioGeneratorTonie 3 | Audio output generator that plays Opus audio files 4 | 5 | Copyright (C) 2020 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | /* 21 | #include "AudioGeneratorTonie.h" 22 | 23 | AudioGeneratorTonie::AudioGeneratorTonie() 24 | { 25 | of = nullptr; 26 | buff = nullptr; 27 | buffPtr = 0; 28 | buffLen = 0; 29 | running = false; 30 | } 31 | 32 | AudioGeneratorTonie::~AudioGeneratorTonie() 33 | { 34 | if (of) op_free(of); 35 | of = nullptr; 36 | free(buff); 37 | buff = nullptr; 38 | } 39 | 40 | #define OPUS_BUFF 512 41 | 42 | bool AudioGeneratorTonie::begin(AudioFileSource *source, AudioOutput *output) 43 | { 44 | buff = (int16_t*)malloc(OPUS_BUFF * sizeof(int16_t)); 45 | if (!buff) return false; 46 | 47 | if (!source) return false; 48 | file = source; 49 | if (!output) return false; 50 | this->output = output; 51 | if (!file->isOpen()) return false; // Error 52 | 53 | //TODO? 54 | file->seek(4096, SEEK_CUR); //skip first sector 55 | 56 | of = op_open_callbacks((void*)this, &cb, nullptr, 0, nullptr); 57 | if (!of) return false; 58 | 59 | prev_li = -1; 60 | lastSample[0] = 0; 61 | lastSample[1] = 0; 62 | 63 | buffPtr = 0; 64 | buffLen = 0; 65 | 66 | output->begin(); 67 | 68 | // These are fixed by Opus 69 | output->SetRate(48000); 70 | output->SetBitsPerSample(16); 71 | output->SetChannels(2); 72 | 73 | running = true; 74 | return true; 75 | } 76 | 77 | bool AudioGeneratorTonie::loop() 78 | { 79 | 80 | if (!running) goto done; 81 | 82 | if (!output->ConsumeSample(lastSample)) goto done; // Try and send last buffered sample 83 | 84 | do { 85 | if (buffPtr == buffLen) { 86 | int ret = op_read_stereo(of, (opus_int16 *)buff, OPUS_BUFF); 87 | if (ret == OP_HOLE) { 88 | // fprintf(stderr,"\nHole detected! Corrupt file segment?\n"); 89 | continue; 90 | } else if (ret < 0) { 91 | running = false; 92 | goto done; 93 | } 94 | buffPtr = 0; 95 | buffLen = ret * 2; 96 | } 97 | 98 | lastSample[AudioOutput::LEFTCHANNEL] = buff[buffPtr] & 0xffff; 99 | lastSample[AudioOutput::RIGHTCHANNEL] = buff[buffPtr+1] & 0xffff; 100 | buffPtr += 2; 101 | } while (running && output->ConsumeSample(lastSample)); 102 | 103 | done: 104 | file->loop(); 105 | output->loop(); 106 | 107 | return running; 108 | } 109 | 110 | bool AudioGeneratorTonie::stop() 111 | { 112 | if (of) op_free(of); 113 | of = nullptr; 114 | free(buff); 115 | buff = nullptr; 116 | running = false; 117 | output->stop(); 118 | return true; 119 | } 120 | 121 | bool AudioGeneratorTonie::isRunning() 122 | { 123 | return running; 124 | } 125 | 126 | 127 | int AudioGeneratorTonie::read_cb(unsigned char *_ptr, int _nbytes) { 128 | if (_nbytes == 0) return 0; 129 | _nbytes = file->read(_ptr, _nbytes); 130 | if (_nbytes == 0) return -1; 131 | return _nbytes; 132 | } 133 | 134 | int AudioGeneratorTonie::seek_cb(opus_int64 _offset, int _whence) { 135 | if (!file->seek((int32_t)_offset, _whence)) return -1; 136 | return 0; 137 | } 138 | 139 | opus_int64 AudioGeneratorTonie::tell_cb() { 140 | return file->getPos(); 141 | } 142 | 143 | int AudioGeneratorTonie::close_cb() { 144 | // NO OP, we close in main loop 145 | return 0; 146 | } 147 | */ -------------------------------------------------------------------------------- /AudioGeneratorTonie.h: -------------------------------------------------------------------------------- 1 | /* 2 | AudioGeneratorTonie 3 | Audio output generator that plays Opus audio files 4 | 5 | Copyright (C) 2020 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _AUDIOGENERATORTONIE_H 22 | #define _AUDIOGENERATORTONIE_H 23 | 24 | #include 25 | //#include "libopus/opus.h" 26 | //#include "opusfile/opusfile.h" 27 | 28 | class AudioGeneratorTonie : public AudioGenerator 29 | { 30 | /* 31 | public: 32 | AudioGeneratorTonie(); 33 | virtual ~AudioGeneratorTonie() override; 34 | virtual bool begin(AudioFileSource *source, AudioOutput *output) override; 35 | virtual bool loop() override; 36 | virtual bool stop() override; 37 | virtual bool isRunning() override; 38 | 39 | protected: 40 | // Opus callbacks, need static functions to bounce into C++ from C 41 | static int OPUS_read(void *_stream, unsigned char *_ptr, int _nbytes) { 42 | return static_cast(_stream)->read_cb(_ptr, _nbytes); 43 | } 44 | static int OPUS_seek(void *_stream, opus_int64 _offset, int _whence) { 45 | return static_cast(_stream)->seek_cb(_offset, _whence); 46 | } 47 | static opus_int64 OPUS_tell(void *_stream) { 48 | return static_cast(_stream)->tell_cb(); 49 | } 50 | static int OPUS_close(void *_stream) { 51 | return static_cast(_stream)->close_cb(); 52 | } 53 | 54 | // Actual Opus callbacks 55 | int read_cb(unsigned char *_ptr, int _nbytes); 56 | int seek_cb(opus_int64 _offset, int _whence); 57 | opus_int64 tell_cb(); 58 | int close_cb(); 59 | 60 | private: 61 | OpusFileCallbacks cb = {OPUS_read, OPUS_seek, OPUS_tell, OPUS_close}; 62 | OggOpusFile *of; 63 | int prev_li; // To detect changes in streams 64 | 65 | int16_t *buff; 66 | uint32_t buffPtr; 67 | uint32_t buffLen; 68 | */ 69 | }; 70 | 71 | #endif 72 | 73 | -------------------------------------------------------------------------------- /AudioGeneratorWAVStatic.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AudioGeneratorWAVStatic 3 | Audio output generator that reads 8 and 16-bit WAV files 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | 22 | #include "AudioGeneratorWAVStatic.h" 23 | 24 | 25 | AudioGeneratorWAVStatic::~AudioGeneratorWAVStatic(){} 26 | 27 | bool AudioGeneratorWAVStatic::stop() 28 | { 29 | if (!running) return true; 30 | running = false; 31 | output->stop(); 32 | return file->close(); 33 | } 34 | 35 | bool AudioGeneratorWAVStatic::isRunning() 36 | { 37 | return running; 38 | } 39 | 40 | 41 | // Handle buffered reading, reload each time we run out of data 42 | bool AudioGeneratorWAVStatic::GetBufferedData(int bytes, void *dest) 43 | { 44 | if (!running) return false; // Nothing to do here! 45 | uint8_t *p = reinterpret_cast(dest); 46 | while (bytes--) { 47 | // Potentially load next batch of data... 48 | if (buffPtr >= buffLen) { 49 | buffPtr = 0; 50 | uint32_t toRead = availBytes > BUFFER_SIZE ? BUFFER_SIZE : availBytes; 51 | buffLen = file->read( buff, toRead ); 52 | availBytes -= buffLen; 53 | } 54 | if (buffPtr >= buffLen) 55 | return false; // No data left! 56 | *(p++) = buff[buffPtr++]; 57 | } 58 | return true; 59 | } 60 | 61 | bool AudioGeneratorWAVStatic::loop() 62 | { 63 | if (!running) goto done; // Nothing to do here! 64 | 65 | // First, try and push in the stored sample. If we can't, then punt and try later 66 | if (!output->ConsumeSample(lastSample)) goto done; // Can't send, but no error detected 67 | 68 | // Try and stuff the buffer one sample at a time 69 | do 70 | { 71 | if (bitsPerSample == 8) { 72 | uint8_t l, r; 73 | if (!GetBufferedData(1, &l)) stop(); 74 | if (channels == 2) { 75 | if (!GetBufferedData(1, &r)) stop(); 76 | } else { 77 | r = 0; 78 | } 79 | lastSample[AudioOutput::LEFTCHANNEL] = l; 80 | lastSample[AudioOutput::RIGHTCHANNEL] = r; 81 | } else if (bitsPerSample == 16) { 82 | if (!GetBufferedData(2, &lastSample[AudioOutput::LEFTCHANNEL])) stop(); 83 | if (channels == 2) { 84 | if (!GetBufferedData(2, &lastSample[AudioOutput::RIGHTCHANNEL])) stop(); 85 | } else { 86 | lastSample[AudioOutput::RIGHTCHANNEL] = 0; 87 | } 88 | } 89 | } while (running && output->ConsumeSample(lastSample)); 90 | 91 | done: 92 | file->loop(); 93 | output->loop(); 94 | 95 | return running; 96 | } 97 | 98 | 99 | bool AudioGeneratorWAVStatic::ReadWAVInfo() 100 | { 101 | uint32_t u32; 102 | uint16_t u16; 103 | int toSkip; 104 | 105 | // WAV specification document: 106 | // https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf 107 | 108 | // Header == "RIFF" 109 | if (!ReadU32(&u32)) { 110 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 111 | return false; 112 | }; 113 | if (u32 != 0x46464952) { 114 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, invalid RIFF header, got: %08X \n"), (uint32_t) u32); 115 | return false; 116 | } 117 | 118 | // Skip ChunkSize 119 | if (!ReadU32(&u32)) { 120 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 121 | return false; 122 | }; 123 | 124 | // Format == "WAVE" 125 | if (!ReadU32(&u32)) { 126 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 127 | return false; 128 | }; 129 | if (u32 != 0x45564157) { 130 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, invalid WAVE header, got: %08X \n"), (uint32_t) u32); 131 | return false; 132 | } 133 | 134 | // there might be JUNK or PAD - ignore it by continuing reading until we get to "fmt " 135 | while (1) { 136 | if (!ReadU32(&u32)) { 137 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 138 | return false; 139 | }; 140 | if (u32 == 0x20746d66) break; // 'fmt ' 141 | }; 142 | 143 | // subchunk size 144 | if (!ReadU32(&u32)) { 145 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 146 | return false; 147 | }; 148 | if (u32 == 16) { toSkip = 0; } 149 | else if (u32 == 18) { toSkip = 18 - 16; } 150 | else if (u32 == 40) { toSkip = 40 - 16; } 151 | else { 152 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, appears not to be standard PCM \n")); 153 | return false; 154 | } // we only do standard PCM 155 | 156 | // AudioFormat 157 | if (!ReadU16(&u16)) { 158 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 159 | return false; 160 | }; 161 | if (u16 != 1) { 162 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, AudioFormat appears not to be standard PCM \n")); 163 | return false; 164 | } // we only do standard PCM 165 | 166 | // NumChannels 167 | if (!ReadU16(&channels)) { 168 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 169 | return false; 170 | }; 171 | if ((channels<1) || (channels>2)) { 172 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, only mono and stereo are supported \n")); 173 | return false; 174 | } // Mono or stereo support only 175 | 176 | // SampleRate 177 | if (!ReadU32(&sampleRate)) { 178 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 179 | return false; 180 | }; 181 | if (sampleRate < 1) { 182 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, unknown sample rate \n")); 183 | return false; 184 | } // Weird rate, punt. Will need to check w/DAC to see if supported 185 | 186 | // Ignore byterate and blockalign 187 | if (!ReadU32(&u32)) { 188 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 189 | return false; 190 | }; 191 | if (!ReadU16(&u16)) { 192 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 193 | return false; 194 | }; 195 | 196 | // Bits per sample 197 | if (!ReadU16(&bitsPerSample)) { 198 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 199 | return false; 200 | }; 201 | if ((bitsPerSample!=8) && (bitsPerSample != 16)) { 202 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, only 8 or 16 bits is supported \n")); 203 | return false; 204 | } // Only 8 or 16 bits 205 | 206 | // Skip any extra header 207 | while (toSkip) { 208 | uint8_t ign; 209 | if (!ReadU8(&ign)) { 210 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 211 | return false; 212 | }; 213 | toSkip--; 214 | } 215 | 216 | // look for data subchunk 217 | do { 218 | // id == "data" 219 | if (!ReadU32(&u32)) { 220 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 221 | return false; 222 | }; 223 | if (u32 == 0x61746164) break; // "data" 224 | // Skip size, read until end of chunk 225 | if (!ReadU32(&u32)) { 226 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 227 | return false; 228 | }; 229 | if(!file->seek(u32, SEEK_CUR)) { 230 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data, seek failed\n")); 231 | return false; 232 | } 233 | } while (1); 234 | if (!file->isOpen()) { 235 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: cannot read WAV, file is not open\n")); 236 | return false; 237 | }; 238 | 239 | // Skip size, read until end of file... 240 | if (!ReadU32(&u32)) { 241 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::ReadWAVInfo: failed to read WAV data\n")); 242 | return false; 243 | }; 244 | availBytes = u32; 245 | 246 | buffPtr = 0; 247 | buffLen = 0; 248 | 249 | return true; 250 | } 251 | 252 | bool AudioGeneratorWAVStatic::begin(AudioFileSource *source, AudioOutput *output) { 253 | running = false; 254 | buffPtr = 0; 255 | buffLen = 0; 256 | 257 | if (!source) { 258 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: failed: invalid source\n")); 259 | return false; 260 | } 261 | file = source; 262 | if (!output) { 263 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: invalid output\n")); 264 | return false; 265 | } 266 | this->output = output; 267 | if (!file->isOpen()) { 268 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: file not open\n")); 269 | return false; 270 | } // Error 271 | 272 | if (!ReadWAVInfo()) { 273 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: failed during ReadWAVInfo\n")); 274 | return false; 275 | } 276 | 277 | if (!output->SetRate( sampleRate )) { 278 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: failed to SetRate in output\n")); 279 | return false; 280 | } 281 | if (!output->SetBitsPerSample( bitsPerSample )) { 282 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: failed to SetBitsPerSample in output\n")); 283 | return false; 284 | } 285 | if (!output->SetChannels( channels )) { 286 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: failed to SetChannels in output\n")); 287 | return false; 288 | } 289 | if (!output->begin()) { 290 | audioLogger->printf_P(PSTR("AudioGeneratorWAVStatic::begin: output's begin did not return true\n")); 291 | return false; 292 | } 293 | 294 | running = true; 295 | 296 | return true; 297 | } 298 | -------------------------------------------------------------------------------- /AudioGeneratorWAVStatic.h: -------------------------------------------------------------------------------- 1 | /* 2 | AudioGeneratorWAV 3 | Audio output generator that reads 8 and 16-bit WAV files 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _AUDIOGENERATORWAVSTATIC_H 22 | #define _AUDIOGENERATORWAVSTATIC_H 23 | 24 | #include "AudioGenerator.h" 25 | 26 | class AudioGeneratorWAVStatic : public AudioGenerator 27 | { 28 | public: 29 | virtual ~AudioGeneratorWAVStatic() override; 30 | virtual bool begin(AudioFileSource *source, AudioOutput *output) override; 31 | virtual bool loop() override; 32 | virtual bool stop() override; 33 | virtual bool isRunning() override; 34 | 35 | private: 36 | bool ReadU32(uint32_t *dest) { return file->read(reinterpret_cast(dest), 4); } 37 | bool ReadU16(uint16_t *dest) { return file->read(reinterpret_cast(dest), 2); } 38 | bool ReadU8(uint8_t *dest) { return file->read(reinterpret_cast(dest), 1); } 39 | bool GetBufferedData(int bytes, void *dest); 40 | bool ReadWAVInfo(); 41 | 42 | 43 | protected: 44 | // WAV info 45 | uint16_t channels; 46 | uint32_t sampleRate; 47 | uint16_t bitsPerSample; 48 | 49 | uint32_t availBytes; 50 | 51 | // We need to buffer some data in-RAM to avoid doing 1000s of small reads 52 | //uint32_t buffSize; 53 | //uint8_t *buff; 54 | uint16_t buffPtr; 55 | uint16_t buffLen; 56 | 57 | static const uint16_t BUFFER_SIZE = 256; 58 | uint8_t buff[BUFFER_SIZE]; 59 | }; 60 | 61 | #endif 62 | 63 | -------------------------------------------------------------------------------- /AudioOutputCC3200I2S.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AudioOutputCC3200I2S 3 | Base class for I2S interface port 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | 22 | #include "AudioOutputCC3200I2S.h" 23 | 24 | AudioOutputCC3200I2S::AudioOutputCC3200I2S(BoxAudioBufferTriple* audioBuffer) 25 | { 26 | this->audioBuffer = audioBuffer; 27 | this->i2sOn = false; 28 | i2sOn = true; 29 | mono = false; 30 | bps = 16; 31 | channels = 2; 32 | SetGain(1.0); 33 | SetRate(16000); // Default 34 | } 35 | 36 | AudioOutputCC3200I2S::~AudioOutputCC3200I2S() 37 | { 38 | i2sOn = false; 39 | } 40 | 41 | bool AudioOutputCC3200I2S::SetRate(int hz) 42 | { 43 | this->hertz = hz; 44 | 45 | uint32_t clock; //(Num of bits * STEREO * sampling) 46 | clock = 16*2*hz;//22050; 47 | 48 | MAP_PRCMI2SClockFreqSet(clock); 49 | MAP_I2SConfigSetExpClk(I2S_BASE, clock, clock, I2S_SLOT_SIZE_16|I2S_PORT_DMA); 50 | 51 | //TODO 52 | return true; 53 | } 54 | int AudioOutputCC3200I2S::GetRate() { 55 | return this->hertz; 56 | } 57 | 58 | bool AudioOutputCC3200I2S::SetBitsPerSample(int bits) 59 | { 60 | if ( (bits != 16) && (bits != 8) ) return false; 61 | this->bps = bits; 62 | return true; 63 | } 64 | 65 | bool AudioOutputCC3200I2S::SetChannels(int channels) 66 | { 67 | if ( (channels < 1) || (channels > 2) ) return false; 68 | this->channels = channels; 69 | return true; 70 | } 71 | 72 | bool AudioOutputCC3200I2S::SetOutputModeMono(bool mono) 73 | { 74 | this->mono = mono; 75 | return true; 76 | } 77 | 78 | bool AudioOutputCC3200I2S::ConsumeSample(int16_t sample[2]) 79 | { 80 | BoxAudioBufferTriple::BufferStruct* writeBuffer = audioBuffer->getBuffer(BoxAudioBufferTriple::BufferType::WRITE); 81 | if (writeBuffer->position >= writeBuffer->size) { 82 | if (audioBuffer->flip(BoxAudioBufferTriple::BufferType::WRITE)) { 83 | writeBuffer = audioBuffer->getBuffer(BoxAudioBufferTriple::BufferType::WRITE); 84 | writeBuffer->state = BoxAudioBufferTriple::BufferState::WRITING; 85 | } else { 86 | return false; 87 | } 88 | } 89 | 90 | int16_t ms[2]; 91 | 92 | ms[0] = sample[0]; 93 | ms[1] = sample[1]; 94 | MakeSampleStereo16( ms ); 95 | 96 | if (this->mono && this->channels == 2) { 97 | // Average the two samples and overwrite 98 | int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; 99 | ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff; 100 | } else if (this->channels == 1) { 101 | ms[LEFTCHANNEL] = ms[RIGHTCHANNEL]; 102 | } 103 | 104 | writeBuffer->buffer[writeBuffer->position++] = ms[LEFTCHANNEL]; 105 | writeBuffer->buffer[writeBuffer->position++] = ms[RIGHTCHANNEL]; 106 | return true; 107 | } 108 | 109 | void AudioOutputCC3200I2S::flush() { 110 | if (!writeEmptyBuffer()) { 111 | writeEmptyBuffer(); 112 | } 113 | while(!audioBuffer->isEmpty()) {} 114 | } 115 | 116 | bool AudioOutputCC3200I2S::stop() { 117 | flush(); 118 | return true; 119 | } 120 | bool AudioOutputCC3200I2S::begin() { 121 | return true; 122 | } 123 | 124 | bool AudioOutputCC3200I2S::writeEmptyBuffer() { 125 | BoxAudioBufferTriple::BufferStruct* buffer = audioBuffer->getBuffer(BoxAudioBufferTriple::BufferType::WRITE); 126 | 127 | bool isBufferEmpty = (buffer->position == 0); 128 | if (isBufferEmpty) 129 | buffer->state = BoxAudioBufferTriple::BufferState::WRITING;; 130 | 131 | while (buffer->size < buffer->position) { 132 | buffer->buffer[buffer->position++] = 0x00; 133 | } 134 | while (!audioBuffer->flip(BoxAudioBufferTriple::BufferType::WRITE)) {} 135 | return isBufferEmpty; 136 | } -------------------------------------------------------------------------------- /AudioOutputCC3200I2S.h: -------------------------------------------------------------------------------- 1 | /* 2 | AudioOutputCC3200I2S 3 | Base class for an I2S output port 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _AUDIOOUTPUTCC3200I2S_H 22 | #define _AUDIOOUTPUTCC3200I2S_H 23 | 24 | #include 25 | 26 | #include "BoxAudioBufferTriple.h" 27 | #include 28 | #include 29 | #include 30 | 31 | class AudioOutputCC3200I2S : public AudioOutput 32 | { 33 | public: 34 | AudioOutputCC3200I2S(BoxAudioBufferTriple* audioBuffer); 35 | virtual ~AudioOutputCC3200I2S() override; 36 | virtual bool SetRate(int hz) override; 37 | virtual bool SetBitsPerSample(int bits) override; 38 | virtual bool SetChannels(int channels) override; 39 | virtual bool ConsumeSample(int16_t sample[2]) override; 40 | virtual void flush() override; 41 | virtual bool stop() override; 42 | virtual bool begin() override; 43 | 44 | bool SetOutputModeMono(bool mono); // Force mono output no matter the input 45 | 46 | int GetRate(); 47 | 48 | bool resample; 49 | uint32_t resampleMaxRate; 50 | 51 | protected: 52 | virtual int AdjustI2SRate(int hz) { return hz; } 53 | uint8_t portNo; 54 | int output_mode; 55 | bool mono; 56 | bool i2sOn; 57 | int dma_buf_count; 58 | // We can restore the old values and free up these pins when in NoDAC mode 59 | uint32_t orig_bck; 60 | uint32_t orig_ws; 61 | 62 | BoxAudioBufferTriple* audioBuffer; 63 | 64 | bool writeEmptyBuffer(); 65 | 66 | uint8_t resampleRate = 1; 67 | }; 68 | 69 | #endif 70 | 71 | -------------------------------------------------------------------------------- /AudioOutputResample.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AudioOutputResample 3 | Adds additional bufferspace to the output chain 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #include "BaseHeader.h" 22 | #include 23 | #include "AudioOutputResample.h" 24 | 25 | AudioOutputResample::AudioOutputResample(uint32_t maxRate, AudioOutputCC3200I2S *dest) 26 | { 27 | this->sink = dest; 28 | this->leftSample = 0; 29 | this->rightSample = 0; 30 | 31 | this->resampleFactor = 1; 32 | this->resampleCount = 0; 33 | 34 | originalSampleRate = this->sink->GetRate(); 35 | 36 | SetMaxRate(maxRate); 37 | } 38 | 39 | AudioOutputResample::~AudioOutputResample() { } 40 | 41 | bool AudioOutputResample::SetRate(int hz) { 42 | this->originalSampleRate = hz; 43 | if (this->maxSampleRate >= hz) { 44 | this->resampleFactor = 1; 45 | Log.info("AudioOutputResample SetRate=%i", hz); 46 | return this->sink->SetRate(hz); 47 | } 48 | 49 | for (this->resampleFactor = 2; this->resampleFactor < 7; this->resampleFactor++) { // 48000/8000 50 | if (this->maxSampleRate >= (hz / this->resampleFactor)) 51 | break; 52 | } 53 | Log.info("AudioOutputResample limited SetRate=%i, hz=%i, resampleFactor=%i", hz / this->resampleFactor, hz, this->resampleFactor); 54 | return sink->SetRate(hz / this->resampleFactor); 55 | } 56 | void AudioOutputResample::SetMaxRate(uint32_t hz) { 57 | switch (hz) 58 | { 59 | case 48000: 60 | case 44100: 61 | case 32000: 62 | case 24000: 63 | case 22050: 64 | case 16000: 65 | case 11025: 66 | case 8000: 67 | this->maxSampleRate = hz; 68 | break; 69 | default: 70 | this->maxSampleRate = 48000; 71 | } 72 | SetRate(this->originalSampleRate); 73 | } 74 | uint32_t AudioOutputResample::GetMaxRate() { 75 | return this->maxSampleRate; 76 | } 77 | int AudioOutputResample::GetRate() { 78 | return this->sink->GetRate(); 79 | } 80 | 81 | bool AudioOutputResample::SetBitsPerSample(int bits) 82 | { 83 | return this->sink->SetBitsPerSample(bits); 84 | } 85 | 86 | bool AudioOutputResample::SetChannels(int channels) 87 | { 88 | return this->sink->SetChannels(channels); 89 | } 90 | 91 | bool AudioOutputResample::begin() 92 | { 93 | return this->sink->begin(); 94 | } 95 | 96 | bool AudioOutputResample::ConsumeSample(int16_t sample[2]) 97 | { 98 | if (1==0) { //Slow 99 | this->leftSample += sample[0]; 100 | this->rightSample += sample[1]; 101 | this->resampleCount++; 102 | if (this->resampleCount >= this->resampleFactor) { 103 | int16_t s[2] = {(int16_t)(leftSample/this->resampleFactor), (int16_t)(rightSample/this->resampleFactor)}; 104 | if (!sink->ConsumeSample(s)) { 105 | return false; 106 | } else { 107 | this->leftSample = 0; 108 | this->rightSample = 0; 109 | this->resampleCount = 0; 110 | } 111 | } 112 | } else { //Fast 113 | this->resampleCount++; 114 | if (this->resampleCount >= this->resampleFactor) { 115 | if (!sink->ConsumeSample(sample)) { 116 | return false; 117 | } else { 118 | this->resampleCount = 0; 119 | } 120 | } 121 | } 122 | return true; 123 | } 124 | 125 | bool AudioOutputResample::stop() 126 | { 127 | return this->sink->stop(); 128 | } 129 | 130 | 131 | -------------------------------------------------------------------------------- /AudioOutputResample.h: -------------------------------------------------------------------------------- 1 | /* 2 | AudioOutputResample 3 | Adds additional bufferspace to the output chain 4 | 5 | Copyright (C) 2017 Earle F. Philhower, III 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | */ 20 | 21 | #ifndef _AUDIOOUTPUTRESAMPLE_H 22 | #define _AUDIOOUTPUTRESAMPLE_H 23 | 24 | #include 25 | #include "AudioOutputCC3200I2S.h" 26 | 27 | class AudioOutputResample : public AudioOutput 28 | { 29 | public: 30 | AudioOutputResample(uint32_t maxSampleRate, AudioOutputCC3200I2S *dest); 31 | virtual ~AudioOutputResample() override; 32 | virtual bool SetRate(int hz) override; 33 | virtual bool SetBitsPerSample(int bits) override; 34 | virtual bool SetChannels(int channels) override; 35 | virtual bool begin() override; 36 | virtual bool ConsumeSample(int16_t sample[2]) override; 37 | virtual bool stop() override; 38 | 39 | int GetRate(); 40 | uint32_t GetMaxRate(); 41 | void SetMaxRate(uint32_t hz); 42 | 43 | protected: 44 | AudioOutputCC3200I2S *sink; 45 | int32_t leftSample; 46 | int32_t rightSample; 47 | uint32_t maxSampleRate; 48 | uint8_t resampleFactor; 49 | uint8_t resampleCount; 50 | 51 | uint32_t originalSampleRate; 52 | }; 53 | 54 | #endif 55 | 56 | -------------------------------------------------------------------------------- /BaseHeader.h: -------------------------------------------------------------------------------- 1 | #ifndef BaseHeader_h 2 | #define BaseHeader_h 3 | 4 | #include "ConfigStatic.h" 5 | #include 6 | #include 7 | #include "ArduinoJson-v6.15.1.h" 8 | #include "BoxConfig.h" 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /BoxAccelerometer.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxAccelerometer.h" 2 | #include "BoxEvents.h" 3 | 4 | void BoxAccelerometer::begin() { 5 | pinMode(62, OUTPUT); 6 | digitalWrite(62, HIGH); 7 | 8 | setInterval(250); 9 | Log.info("Init Accelerometer..."); 10 | 11 | if (!_accel.begin()) 12 | Log.error("...not found!"); 13 | 14 | _accel.setupPL(); 15 | 16 | 17 | _accel.writeRegister(CTRL_REG1, 0x02); //Original 0x02 //F_READ 18 | _accel.writeRegister(XYZ_DATA_CFG, 0x02); //Original 0x02 //FS1 19 | _accel.writeRegister(CTRL_REG2, 0x00); //Original 0x00 //Standby 20 | _accel.writeRegister(F_SETUP, 0x00); //Original 0x00 21 | _accel.writeRegister(TRIG_CFG, 0x08); //Original 0x08 //Trig_PULSE/ZSPEFE/ELE 22 | _accel.writeRegister(PULSE_CFG, 0x54); //Original 0x54 //YSPEFE 23 | _accel.setupTap(0x1B, 0x3F, 0x3F); //Original 0x1B, 0x3F, 0x3F 24 | _accel.writeRegister(PULSE_TMLT, 0x28); //Original 0x28 //TMLT3/TMLT5 25 | _accel.writeRegister(PULSE_LTCY, 0x7F); //Original 0x7F //LTCY6/LTCY5/LTCY4/LTCY3/LTCY2/LTCY1/LTCY0 26 | _accel.writeRegister(HP_FILTER_CUTOFF, 0x10); //Original 0x10 //Pulse_LPF_EN 27 | 28 | _accel.writeRegister(CTRL_REG3, 0x12); //Original 0x12 //WAKE_PULSE/IPOL 29 | _accel.writeRegister(CTRL_REG4, 0x40); //Original 0x40 //INT_EN_FIFO 30 | _accel.writeRegister(CTRL_REG5, 0x40); //Original 0x40 //INT_CFG_FIFO INT1 31 | _accel.writeRegister(CTRL_REG1, 0x03); //Original 0x03 //F_READ/ACTIVE 32 | 33 | Log.info("...done"); 34 | } 35 | 36 | void BoxAccelerometer::loop() { 37 | if (_accel.available()) { 38 | //Log.verbose("Accelerometer=(%i, %i, %i), Orient=%i", _accel.getX(), _accel.getY(), _accel.getZ(), _accel.readOrientation()); 39 | 40 | Orientation orient = (Orientation)_accel.readOrientation(); 41 | if (orient == Orientation::EARS_UP2) { 42 | orient = Orientation::EARS_UP; 43 | } else if (orient == Orientation::EARS_DOWN2) { 44 | orient = Orientation::EARS_DOWN; 45 | } 46 | 47 | if (_orientation != orient) { 48 | _orientation = orient; 49 | Events.handleAccelerometerOrientationEvent(_orientation); 50 | } 51 | 52 | uint8_t tap = _accel.readTap(); 53 | if (tap) { 54 | bool AxZ = (tap&0b1000000)>0; //event on axis 55 | bool AxY = (tap&0b0100000)>0; 56 | bool AxX = (tap&0b0010000)>0; 57 | //bool DPE = (tap&0b0001000)>0; //double 58 | bool PolZ = !((tap&0b0000100)>0); //0=positive 1=negative 59 | bool PolY = !((tap&0b0000010)>0); 60 | bool PolX = !((tap&0b0000001)>0); 61 | 62 | //X+ = box bottom 63 | //X- = box top 64 | //Y+ = box back left (big ear) 65 | //Y- = box front right (speaker, small ear) 66 | //Z+ = box back right (small ear) 67 | //Z- = box front left (speaker, big ear) 68 | 69 | //Something wrong, only blinks red or greenyellow 70 | TapOn tapOn = TapOn::NONE; 71 | if (AxX) { 72 | if (PolX) { 73 | tapOn = TapOn::BOTTOM; 74 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Red, 2); 75 | } else { 76 | tapOn = TapOn::TOP; 77 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Orange, 2); 78 | } 79 | } 80 | if (AxY && AxZ) { 81 | if (PolY && PolZ) { 82 | tapOn = TapOn::BACK; 83 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Blue, 3); 84 | } else if (!PolY && !PolZ) { 85 | tapOn = TapOn::FRONT; 86 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Violet, 3); 87 | } else if (PolY && !PolZ) { 88 | tapOn = TapOn::LEFT; 89 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Green, 3); 90 | } else if (!PolY && PolZ) { 91 | tapOn = TapOn::RIGHT; 92 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::GreenYellow, 3); 93 | } 94 | } else if (AxY) { 95 | if (PolY) { 96 | tapOn = TapOn::LEFT_BACK; 97 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Blue, 3); 98 | } else { 99 | tapOn = TapOn::LEFT_FRONT; 100 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Violet, 3); 101 | } 102 | } else if (AxZ) { 103 | if (PolZ) { 104 | tapOn = TapOn::RIGHT_BACK; 105 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Green, 3); 106 | } else { 107 | tapOn = TapOn::RIGHT_FRONT; 108 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::GreenYellow, 3); 109 | } 110 | } 111 | 112 | Log.disableNewline(true); 113 | Log.verbose("Tap recieved %B, direction=%X, ", tap, tapOn); 114 | Log.disableNewline(false); 115 | switch (tapOn) 116 | { 117 | case TapOn::LEFT: 118 | Log.printfln("LEFT"); 119 | break; 120 | 121 | case TapOn::RIGHT: 122 | Log.printfln("RIGHT"); 123 | break; 124 | 125 | case TapOn::FRONT: 126 | Log.printfln("FRONT"); 127 | break; 128 | 129 | case TapOn::BACK: 130 | Log.printfln("BACK"); 131 | break; 132 | 133 | case TapOn::TOP: 134 | Log.printfln("TOP"); 135 | break; 136 | 137 | case TapOn::BOTTOM: 138 | Log.printfln("BOTTOM"); 139 | break; 140 | 141 | case TapOn::LEFT_FRONT: 142 | Log.printfln("LEFT_FRONT"); 143 | break; 144 | 145 | case TapOn::RIGHT_FRONT: 146 | Log.printfln("RIGHT_FRONT"); 147 | break; 148 | 149 | case TapOn::LEFT_BACK: 150 | Log.printfln("LEFT_BACK"); 151 | break; 152 | 153 | case TapOn::RIGHT_BACK: 154 | Log.printfln("RIGHT_BACK"); 155 | break; 156 | 157 | default: 158 | break; 159 | Log.printfln("OTHER"); 160 | } 161 | 162 | } 163 | 164 | } 165 | } 166 | 167 | void BoxAccelerometer::reloadConfig() { 168 | 169 | } 170 | 171 | BoxAccelerometer::Orientation BoxAccelerometer::getOrientation() { 172 | return _orientation; 173 | } -------------------------------------------------------------------------------- /BoxAccelerometer.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxAccelerometer_h 2 | #define BoxAccelerometer_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | 7 | #include 8 | 9 | class BoxAccelerometer : public EnhancedThread { 10 | public: 11 | enum class Orientation { 12 | EARS_UP = 0x7, 13 | EARS_DOWN = 0x5, 14 | EARS_FRONT = 0x3, 15 | EARS_BACK = 0x0, 16 | EARS_LEFT = 0x2, 17 | EARS_RIGHT = 0x1, 18 | OTHER = LOCKOUT, 19 | 20 | EARS_UP2 = 0x6, 21 | EARS_DOWN2 = 0x4 22 | }; 23 | enum class TapOn { 24 | NONE = 0x00, 25 | LEFT = 0x01, 26 | RIGHT = 0x02, //17,34,68 - 34 27 | FRONT = 0x04, //16, 17 28 | BACK = 0x08, 29 | TOP = 0x10, //16,32,64,68,24 - 16 30 | BOTTOM = 0x20, //17 31 | 32 | LEFT_FRONT = LEFT + FRONT, 33 | RIGHT_FRONT = RIGHT + FRONT, 34 | LEFT_BACK = LEFT + BACK, 35 | RIGHT_BACK = RIGHT + BACK 36 | }; 37 | 38 | void 39 | begin(), 40 | loop(), 41 | reloadConfig(); 42 | 43 | BoxAccelerometer::Orientation getOrientation(); 44 | 45 | 46 | private: 47 | MMA8452Q _accel; 48 | BoxAccelerometer::Orientation _orientation; 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /BoxAudioBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxAudioBuffer.h" 2 | 3 | void BoxAudioBuffer::init(uint8_t* buffer, uint16_t size) { 4 | _bufferStart = buffer; 5 | _bufferEnd = buffer+size-1; 6 | _bufferSize = size; 7 | 8 | _readPointer = _bufferStart; 9 | _writePointer = _bufferStart; 10 | 11 | _isFull = false; 12 | _isEmpty = true; 13 | } 14 | 15 | void BoxAudioBuffer::logState() { 16 | //noIRQ = true; 17 | Log.info("BoxAudioBuffer bufferStart=%X, _bufferEnd=%X, _bufferSize=%X, _readPointer=%X, _writePointer=%X", _bufferStart, _bufferEnd, _bufferSize, _readPointer, _writePointer); 18 | Log.info(" getBytesReadable()=%X, getBytesReadableBlock()=%X, getBytesWritable()=%X, getBytesWritableBlock()=%X", getBytesReadable(), getBytesReadableBlock(), getBytesWritable(), getBytesWritableBlock()); 19 | //noIRQ = false; 20 | } 21 | 22 | bool BoxAudioBuffer::readPointerEqualsWritePointer() { 23 | return _readPointer == _writePointer; 24 | } 25 | 26 | bool BoxAudioBuffer::isEmpty(uint16_t threshold) { 27 | if (getBytesReadable() <= threshold) { 28 | if (threshold == 0) { 29 | return _isEmpty; 30 | } 31 | return true; 32 | } 33 | return false; 34 | } 35 | bool BoxAudioBuffer::isFull(uint16_t threshold) { 36 | if (getBytesWritable() <= threshold) { 37 | if (threshold == 0) { 38 | return _isFull; 39 | } 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | uint8_t* BoxAudioBuffer::getReadPointer() { 46 | return _readPointer; 47 | } 48 | void BoxAudioBuffer::updateReadPointer(uint16_t readLength) { 49 | uint16_t readableBlock = getBytesReadableBlock(); 50 | if (readLength < readableBlock) { 51 | _readPointer += readLength; 52 | } else { 53 | _readPointer = _bufferStart + (readLength - readableBlock); 54 | } 55 | if (readPointerEqualsWritePointer()) { 56 | _isFull = false; 57 | _isEmpty = true; 58 | } 59 | } 60 | 61 | void BoxAudioBuffer::write(uint8_t* buffer, uint16_t size) { 62 | noIRQ = true; 63 | uint16_t writableBlock = getBytesWritableBlock(); 64 | //Log.info("write *buffer=%X, size=%i, writableBlock=%i", buffer, size, writableBlock); 65 | 66 | if (size <= writableBlock) { 67 | //Log.info(" _memcpy(%X, %X, %i);", _writePointer, buffer, size); 68 | memcpy(_writePointer, buffer, size); 69 | _writePointer += size; 70 | if (_writePointer > _bufferEnd) 71 | _writePointer = _bufferStart; 72 | } else { 73 | //Log.info(" _memcpy(%X, %X, %i);", _writePointer, buffer, writableBlock); 74 | memcpy(_writePointer, buffer, writableBlock); 75 | //Log.info(" _memcpy(%X, %X, %i);", _bufferStart, buffer + writableBlock, size - writableBlock); 76 | memcpy(_bufferStart, buffer + writableBlock, size - writableBlock); 77 | _writePointer = _bufferStart + (size - writableBlock); 78 | } 79 | if (readPointerEqualsWritePointer()) { 80 | _isEmpty = false; 81 | _isFull = true; 82 | } 83 | noIRQ = false; 84 | } 85 | 86 | 87 | uint16_t BoxAudioBuffer::getBytesReadable() { 88 | if (readPointerEqualsWritePointer() && _isEmpty) 89 | return 0; 90 | return _measureDistance(_writePointer, _readPointer); 91 | } 92 | uint16_t BoxAudioBuffer::getBytesReadableBlock() { 93 | if (readPointerEqualsWritePointer() && _isEmpty) 94 | return 0; 95 | return _measureDistanceBlock(_writePointer, _readPointer); 96 | } 97 | 98 | uint16_t BoxAudioBuffer::getBytesWritable() { 99 | if (readPointerEqualsWritePointer() && _isFull) 100 | return 0; 101 | return _measureDistance(_readPointer, _writePointer); 102 | } 103 | uint16_t BoxAudioBuffer::getBytesWritableBlock() { 104 | if (readPointerEqualsWritePointer() && _isFull) 105 | return 0; 106 | return _measureDistanceBlock(_readPointer, _writePointer); 107 | } 108 | 109 | uint16_t BoxAudioBuffer::_measureDistance(uint8_t* p1, uint8_t* p2) { 110 | int32_t offset = p1 - p2; 111 | 112 | if (offset > 0) 113 | return offset; 114 | 115 | return (_bufferEnd - p2 + 1) + (p1 - _bufferStart); 116 | } 117 | uint16_t BoxAudioBuffer::_measureDistanceBlock(uint8_t* p1, uint8_t* p2) { 118 | int32_t offset = p1 - p2; 119 | 120 | if (offset > 0) 121 | return offset; 122 | 123 | return _bufferEnd - p2 + 1; 124 | } -------------------------------------------------------------------------------- /BoxAudioBuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxAudioBuffer_h 2 | #define BoxAudioBuffer_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | class BoxAudioBuffer { 7 | public: 8 | bool noIRQ = false; 9 | 10 | void init(uint8_t* buffer, uint16_t size); 11 | void logState(); 12 | 13 | bool readPointerEqualsWritePointer(); 14 | 15 | bool 16 | isEmpty(uint16_t threshold = 0), 17 | isFull(uint16_t threshold = 0); 18 | 19 | uint8_t* getReadPointer(); 20 | void updateReadPointer(uint16_t readLength); 21 | 22 | void write(uint8_t* buffer, uint16_t size); 23 | 24 | uint16_t 25 | getBytesReadable(), 26 | getBytesReadableBlock(), 27 | getBytesWritable(), 28 | getBytesWritableBlock(); 29 | 30 | private: 31 | bool _ready = false; 32 | 33 | bool 34 | _isFull, 35 | _isEmpty; 36 | 37 | 38 | uint8_t* _bufferStart; 39 | uint8_t* _bufferEnd; 40 | uint16_t _bufferSize; 41 | 42 | uint8_t* _readPointer; 43 | uint8_t* _writePointer; 44 | 45 | uint16_t 46 | _measureDistance(uint8_t* p1, uint8_t* p2), 47 | _measureDistanceBlock(uint8_t* p1, uint8_t* p2); 48 | }; 49 | #endif -------------------------------------------------------------------------------- /BoxAudioBufferTriple.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxAudioBufferTriple.h" 2 | 3 | void BoxAudioBufferTriple::init() { 4 | for (uint16_t i = 0; i < _dataBufferSize; i++) 5 | _dataBuffer[i] = 0x00; 6 | 7 | for (uint8_t i = 0; i < _bufferCount; i++) { 8 | _bufferStruct[i].index = i; 9 | _bufferStruct[i].buffer = (uint16_t*)(_dataBuffer + 2*i*_bufferSize); //bytes 10 | _bufferStruct[i].size = _bufferSize; //words 11 | _bufferStruct[i].position = 0; 12 | _bufferStruct[i].state = BufferState::READY_FOR_WRITE; 13 | _bufferStruct[i].next = &_bufferStruct[i+1]; 14 | } 15 | _bufferStruct[0].state = BufferState::READY_FOR_READ; 16 | _bufferStruct[_bufferCount-1].next = &_bufferStruct[0]; 17 | 18 | _bufferRead = &_bufferStruct[0]; 19 | _bufferWrite = &_bufferStruct[1]; 20 | 21 | } 22 | void BoxAudioBufferTriple::logState() { 23 | Log.info("BoxAudioBufferTriple state"); 24 | for (uint8_t i = 0; i < _bufferCount; i++) 25 | logState(&_bufferStruct[i]); 26 | } 27 | 28 | bool BoxAudioBufferTriple::flip(BoxAudioBufferTriple::BufferType type) { 29 | if (type == BufferType::READ) { 30 | if (_bufferRead->next->state == BufferState::READY_FOR_READ) { 31 | _bufferRead->state = BufferState::READY_FOR_WRITE; 32 | _bufferRead = _bufferRead->next; 33 | _bufferRead->position = 0; 34 | return true; 35 | } 36 | } else if (type == BufferType::WRITE) { 37 | if (_bufferWrite->next->state == BufferState::READY_FOR_WRITE) { 38 | _bufferWrite->state = BufferState::READY_FOR_READ; 39 | _bufferWrite = _bufferWrite->next; 40 | _bufferWrite->position = 0; 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | 47 | void BoxAudioBufferTriple::logState(BoxAudioBufferTriple::BufferStruct* buffer) { 48 | Log.info(" [%i] buffer=%X, size=%X, state=%X, nextBuffer=%X", buffer->index, buffer->buffer, buffer->size, buffer->state, buffer->next->buffer); 49 | } 50 | 51 | BoxAudioBufferTriple::BufferStruct* BoxAudioBufferTriple::getBuffer(BoxAudioBufferTriple::BufferType type) { 52 | switch (type) { 53 | case BufferType::READ: 54 | return _bufferRead; 55 | case BufferType::WRITE: 56 | return _bufferWrite; 57 | case BufferType::WAIT: 58 | return NULL; 59 | } 60 | return NULL; 61 | } 62 | 63 | 64 | bool BoxAudioBufferTriple::isFull() { 65 | if (_bufferWrite->next->state != BufferState::READY_FOR_WRITE) { 66 | return true; 67 | } 68 | return false; 69 | } 70 | bool BoxAudioBufferTriple::isEmpty() { 71 | if (_bufferRead->next->state != BufferState::READY_FOR_READ) { 72 | return true; 73 | } 74 | return false; 75 | } -------------------------------------------------------------------------------- /BoxAudioBufferTriple.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxAudioBufferTriple_h 2 | #define BoxAudioBufferTriple_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | class BoxAudioBufferTriple { 7 | public: 8 | enum class BufferState { 9 | UNKNOWN = 0x00, 10 | READY_FOR_WRITE = 0x01, 11 | READY_FOR_READ = 0x02, 12 | READING = 0x03, 13 | WRITING = 0x04 14 | }; 15 | enum class BufferType { 16 | READ = 0x01, 17 | WRITE = 0x02, 18 | WAIT = 0x03 19 | }; 20 | struct BufferStruct { 21 | uint8_t index; 22 | uint16_t* buffer; 23 | uint16_t size; 24 | uint16_t position; 25 | BufferState state; 26 | BufferStruct* next; 27 | }; 28 | 29 | const static uint16_t I2S_MAX_ELEMENTS = 1024; 30 | const static uint16_t I2S_MAX_BYTES = 2*I2S_MAX_ELEMENTS; 31 | 32 | void init(); 33 | void logState(); 34 | void logState(BoxAudioBufferTriple::BufferStruct* buffer); 35 | 36 | bool isFull(); 37 | bool isEmpty(); 38 | 39 | uint16_t getBufferSize(); 40 | 41 | BufferStruct* getBuffer(BoxAudioBufferTriple::BufferType type); 42 | 43 | bool flip(BoxAudioBufferTriple::BufferType type); 44 | 45 | private: 46 | 47 | BufferStruct* _bufferRead; 48 | BufferStruct* _bufferWrite; 49 | 50 | //static uint16_t _dataBufferSize = _eringbuffer - _ringbuffer; //TODO 51 | //uint8_t* _dataBuffer = (uint8_t*)_ringbuffer; 52 | const static uint16_t _dataBufferSize = 0x4000; 53 | uint8_t* _dataBuffer = (uint8_t*)0x20000000; //lower memory up to 0x4000 length; 54 | //uint8_t __attribute__((section(".blsec"))) _dataBuffer[_dataBufferSize]; 55 | 56 | const static uint16_t _bufferSize = I2S_MAX_ELEMENTS; 57 | const static uint16_t _bufferCount = _dataBufferSize / I2S_MAX_BYTES; 58 | 59 | BufferStruct _bufferStruct[_bufferCount]; 60 | }; 61 | 62 | #endif -------------------------------------------------------------------------------- /BoxBattery.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxBattery.h" 2 | #include "BoxEvents.h" 3 | 4 | void BoxBattery::begin() { 5 | reloadConfig(); 6 | 7 | pinMode(8, INPUT); //Charger pin 8 | 9 | _wasLow = false; 10 | _wasCritical = false; 11 | _readBatteryAdc(); 12 | 13 | batteryTestThread.setInterval(10*60*1000); 14 | batteryTestThread.enabled = false; 15 | 16 | loop(); 17 | logBatteryStatus(); 18 | 19 | setInterval(100); 20 | } 21 | void BoxBattery::loop() { 22 | _readBatteryAdc(); 23 | _charger.read(); 24 | 25 | if (_batteryAdcRaw < _batteryAdcLowRaw || isChargerConnected()) 26 | _batteryAdcLowRaw = _batteryAdcRaw; 27 | 28 | if (_charger.wasPressed()) { 29 | Events.handleBatteryEvent(BatteryEvent::CHR_CONNECT); 30 | } else if (_charger.wasReleased()) { 31 | Events.handleBatteryEvent(BatteryEvent::CHR_DISCONNECT); 32 | } 33 | 34 | if (!isChargerConnected()) { 35 | if (!_wasCritical && isBatteryCritical()) { 36 | _wasCritical = true; 37 | Events.handleBatteryEvent(BatteryEvent::BAT_CRITICAL); 38 | } else if (!_wasLow && isBatteryLow()) { 39 | _wasLow = true; 40 | Events.handleBatteryEvent(BatteryEvent::BAT_LOW); 41 | } 42 | } else { 43 | _wasLow = false; 44 | _wasCritical = false; 45 | } 46 | } 47 | 48 | void BoxBattery::_readBatteryAdc() { 49 | uint16_t adcValue = analogReadAvg(BATTERY_VOLTAGE_PIN, 1); 50 | _batteryAdcRaw = adcValue; 51 | } 52 | 53 | bool BoxBattery::isChargerConnected() { 54 | if (_charger.isPressed()) 55 | return true; 56 | return false; 57 | } 58 | uint16_t BoxBattery::getBatteryAdcRaw() { 59 | return _batteryAdcRaw; 60 | } 61 | uint16_t BoxBattery::getBatteryVoltage() { 62 | return 1000 * getBatteryAdcRaw() / _batteryVoltageFactor; 63 | } 64 | bool BoxBattery::isBatteryLow() { 65 | if (getBatteryAdcRaw() < _batteryLowAdc) 66 | return true; 67 | return false; 68 | } 69 | bool BoxBattery::isBatteryCritical() { 70 | if (getBatteryAdcRaw() < _batteryCriticalAdc) 71 | return true; 72 | return false; 73 | } 74 | 75 | void BoxBattery::logBatteryStatus() { 76 | int voltageDec = getBatteryVoltage(); 77 | int voltageNum = voltageDec / 100; 78 | voltageDec = voltageDec - voltageNum * 100; 79 | 80 | Log.info("Battery Stats:"); 81 | Log.info(" Charging: %T", isChargerConnected()); 82 | Log.info(" ADC Raw: %c", getBatteryAdcRaw()); 83 | Log.info(" Estimated Voltage: %d.%s%dV", voltageNum, (voltageDec<10) ? "0": "", voltageDec); 84 | Log.info(" Battery Low: %T", isBatteryLow()); 85 | Log.info(" Battery Critical: %T", isBatteryCritical()); 86 | } 87 | 88 | void BoxBattery::reloadConfig() { 89 | ConfigStruct* config = Config.get(); 90 | 91 | _batteryVoltageFactor = config->battery.voltageFactor; 92 | _batteryLowAdc = config->battery.lowAdc; 93 | _batteryCriticalAdc = config->battery.criticalAdc; 94 | } 95 | 96 | void BoxBattery::doBatteryTestStep() { 97 | Log.info("Write battery test data..."); 98 | 99 | FileFs file; 100 | if (file.open(_batteryTestFilename, FA_OPEN_APPEND | FA_WRITE)) { 101 | uint16_t voltageDec = getBatteryVoltage(); 102 | uint8_t voltageNum = voltageDec / 100; 103 | voltageDec = voltageDec - voltageNum * 100; 104 | 105 | uint16_t timeRunning = (millis()-_batteryTestStartMillis) / (1000*60); 106 | bool chargerConnected = isChargerConnected(); 107 | uint16_t batteryAdcRaw = getBatteryAdcRaw(); 108 | bool batteryLow = isBatteryLow(); 109 | bool batteryCritical = isBatteryCritical(); 110 | 111 | char output[5+1 +5+1 +5+1 +3+1+5+5+1 +5+1 +5+1 +1]; 112 | sprintf(output, "%hu;%s;%hu;%hu.%s%hu;%s;%s;", 113 | timeRunning, 114 | (chargerConnected ? "true" : "false"), 115 | batteryAdcRaw, 116 | voltageNum, (voltageDec<10) ? "0": "", voltageDec, 117 | (batteryLow ? "true" : "false"), 118 | (batteryCritical ? "true" : "false") 119 | ); 120 | Log.info(output); 121 | file.writeString(output); 122 | 123 | file.writeString("\r\n"); 124 | file.close(); 125 | } else { 126 | Log.error("Couldn't write log %", _batteryTestFilename); 127 | } 128 | } 129 | void BoxBattery::startBatteryTest() { 130 | Log.info("Start battery test..."); 131 | 132 | batteryTestThread.enabled = true; 133 | _batteryTestStartMillis = millis(); 134 | FileFs file; 135 | if (file.open(_batteryTestFilename, FA_CREATE_ALWAYS | FA_WRITE)) { 136 | char output[26+10+10+1]; 137 | 138 | file.writeString("Timestamp;"); 139 | file.writeString("Charging;"); 140 | file.writeString("ADC;"); 141 | file.writeString("Estimated Voltage;"); 142 | file.writeString("Low;"); 143 | file.writeString("Critical;"); 144 | file.writeString("Comments"); 145 | file.writeString("\r\n"); 146 | file.writeString("0;;;;;;"); 147 | sprintf(output, "vFactor=%lu;v3-wav", _batteryVoltageFactor); 148 | file.writeString(output); 149 | file.writeString("\r\n"); 150 | file.close(); 151 | 152 | batteryTestThread.run(); 153 | Box.boxDAC.initBatteryTest(); 154 | } else { 155 | Log.error("Couldn't init battery log %s", _batteryTestFilename); 156 | batteryTestThread.enabled = false; 157 | } 158 | } 159 | void BoxBattery::stopBatteryTest() { 160 | if (!batteryTestThread.enabled) 161 | return; 162 | Log.info("Stop battery test..."); 163 | batteryTestThread.enabled = false; 164 | doBatteryTestStep(); 165 | FileFs file; 166 | if (file.open(_batteryTestFilename, FA_OPEN_APPEND | FA_WRITE)) { 167 | char output[13+5+1]; 168 | uint16_t timeRunning = (millis()-_batteryTestStartMillis) / (1000*60); 169 | sprintf(output, "%hu;;;;;;stopped", timeRunning); 170 | file.writeString(output); 171 | file.writeString("\r\n"); 172 | file.close(); 173 | } else { 174 | Log.error("Couldn't write battery log %s", _batteryTestFilename); 175 | batteryTestThread.enabled = false; 176 | } 177 | } 178 | bool BoxBattery::batteryTestActive() { 179 | return batteryTestThread.enabled; 180 | } 181 | 182 | BoxBattery::BatteryStats BoxBattery::getBatteryStats() { 183 | BoxBattery::BatteryStats stats; 184 | 185 | stats.charging = isChargerConnected(); 186 | stats.low = isBatteryLow(); 187 | stats.critical = isBatteryCritical(); 188 | stats.adcRaw = _batteryAdcRaw; 189 | stats.voltage = getBatteryVoltage(); 190 | stats.testActive = batteryTestActive(); 191 | stats.testActiveMinutes = 0; 192 | if (stats.testActive) 193 | stats.testActiveMinutes = (millis()-_batteryTestStartMillis) / (1000*60); 194 | 195 | return stats; 196 | } -------------------------------------------------------------------------------- /BoxBattery.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxBattery_h 2 | #define BoxBattery_h 3 | 4 | #include "BaseHeader.h" 5 | #include "JC_Button.h" 6 | 7 | #include 8 | 9 | class BoxBattery : public EnhancedThread { 10 | public: 11 | struct BatteryStats { 12 | bool charging; 13 | bool low; 14 | bool critical; 15 | uint16_t adcRaw; 16 | uint16_t voltage; 17 | bool testActive; 18 | uint16_t testActiveMinutes; 19 | }; 20 | 21 | 22 | enum class BatteryEvent { 23 | BAT_LOW, 24 | BAT_CRITICAL, 25 | CHR_CONNECT, 26 | CHR_DISCONNECT 27 | }; 28 | 29 | void 30 | begin(), 31 | loop(), 32 | reloadConfig(); 33 | 34 | uint16_t getBatteryAdcRaw(); 35 | uint16_t getBatteryVoltage(); 36 | 37 | bool 38 | isChargerConnected(), 39 | isBatteryLow(), 40 | isBatteryCritical(); 41 | 42 | void logBatteryStatus(); 43 | 44 | void startBatteryTest(); 45 | void stopBatteryTest(); 46 | bool batteryTestActive(); 47 | 48 | EnhancedThread batteryTestThread; 49 | void doBatteryTestStep(); 50 | 51 | BoxBattery::BatteryStats getBatteryStats(); 52 | 53 | private: 54 | uint32_t _batteryVoltageFactor; 55 | uint16_t _batteryLowAdc; 56 | uint16_t _batteryCriticalAdc; 57 | 58 | Button _charger = Button(8, 25, false, false); 59 | bool _wasLow; 60 | bool _wasCritical; 61 | uint16_t _batteryAdcRaw; 62 | uint16_t _batteryAdcLowRaw; 63 | 64 | const char* _batteryTestFilename = "/revvox/batteryTest.csv"; 65 | uint64_t _batteryTestStartMillis; 66 | 67 | const static uint8_t BATTERY_VOLTAGE_PIN = 60; 68 | void _readBatteryAdc(); 69 | }; 70 | 71 | #endif -------------------------------------------------------------------------------- /BoxButtonEars.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxButtonEars.h" 2 | #include "BoxEvents.h" 3 | 4 | void BoxButtonEars::begin() { 5 | reloadConfig(); 6 | 7 | _earSmall.begin(); 8 | _earBig.begin(); 9 | 10 | setInterval(15); 11 | } 12 | 13 | void BoxButtonEars::loop() { 14 | _earSmall.read(); 15 | if (_earSmall.pressedFor(_earVeryLongPressMs) && _earSmallPressedTime == PressedTime::LONG) { 16 | _earSmallPressedTime = PressedTime::VERY_LONG; 17 | if (!_earBig.isPressed() && _earBigPressedTime == PressedTime::NOT) 18 | Events.handleEarEvent(EarButton::SMALL, PressedType::PRESS, _earSmallPressedTime); 19 | } else if (_earSmall.pressedFor(_earLongPressMs) && _earSmallPressedTime == PressedTime::SHORT) { 20 | _earSmallPressedTime = PressedTime::LONG; 21 | if (!_earBig.isPressed() && _earBigPressedTime == PressedTime::NOT) 22 | Events.handleEarEvent(EarButton::SMALL, PressedType::PRESS, _earSmallPressedTime); 23 | } else if (_earSmall.isPressed() && _earSmallPressedTime == PressedTime::NOT) { 24 | _earSmallPressedTime = PressedTime::SHORT; 25 | if (!_earBig.isPressed() && _earBigPressedTime == PressedTime::NOT) 26 | Events.handleEarEvent(EarButton::SMALL, PressedType::PRESS, _earSmallPressedTime); 27 | } 28 | 29 | _earBig.read(); 30 | if (_earBig.pressedFor(_earVeryLongPressMs) && _earBigPressedTime == PressedTime::LONG) { 31 | _earBigPressedTime = PressedTime::VERY_LONG; 32 | if (!_earSmall.isPressed() && _earSmallPressedTime == PressedTime::NOT) 33 | Events.handleEarEvent(EarButton::BIG, PressedType::PRESS, _earBigPressedTime); 34 | } else if (_earBig.pressedFor(_earLongPressMs) && _earBigPressedTime == PressedTime::SHORT) { 35 | _earBigPressedTime = PressedTime::LONG; 36 | if (!_earSmall.isPressed() && _earSmallPressedTime == PressedTime::NOT) 37 | Events.handleEarEvent(EarButton::BIG, PressedType::PRESS, _earBigPressedTime); 38 | } else if (_earBig.isPressed() && _earBigPressedTime == PressedTime::NOT) { 39 | _earBigPressedTime = PressedTime::SHORT; 40 | if (!_earSmall.isPressed() && _earSmallPressedTime == PressedTime::NOT) 41 | Events.handleEarEvent(EarButton::BIG, PressedType::PRESS, _earBigPressedTime); 42 | } 43 | 44 | if (_earSmallPressedTime == PressedTime::VERY_LONG && _earBigPressedTime == PressedTime::VERY_LONG && _earBothPressedTime == PressedTime::LONG) { 45 | _earBothPressedTime = PressedTime::VERY_LONG; 46 | Events.handleEarEvent(EarButton::BOTH, PressedType::PRESS, _earBothPressedTime); 47 | } else if (_earSmallPressedTime == PressedTime::LONG && _earBigPressedTime == PressedTime::LONG && _earBothPressedTime == PressedTime::SHORT) { 48 | _earBothPressedTime = PressedTime::LONG; 49 | Events.handleEarEvent(EarButton::BOTH, PressedType::PRESS, _earBothPressedTime); 50 | } else if (_earSmallPressedTime == PressedTime::SHORT && _earBigPressedTime == PressedTime::SHORT && _earBothPressedTime == PressedTime::NOT) { 51 | _earBothPressedTime = PressedTime::SHORT; 52 | Events.handleEarEvent(EarButton::BOTH, PressedType::PRESS, _earBothPressedTime); 53 | } 54 | 55 | if (_earBothPressedTime == PressedTime::NOT) { 56 | if (_earSmall.wasReleased()) { 57 | Events.handleEarEvent(EarButton::SMALL, PressedType::RELEASE, _earSmallPressedTime); 58 | _earSmallPressedTime = PressedTime::NOT; 59 | } else if (_earBig.wasReleased()) { 60 | Events.handleEarEvent(EarButton::BIG, PressedType::RELEASE, _earBigPressedTime); 61 | _earBigPressedTime = PressedTime::NOT; 62 | } 63 | } else if (_earSmall.wasReleased() || _earBig.wasReleased()) { 64 | //TODO Prevent release event of other ear 65 | Events.handleEarEvent(EarButton::BOTH, PressedType::RELEASE, _earBothPressedTime); 66 | _earBothPressedTime = PressedTime::NOT; 67 | _earSmallPressedTime = PressedTime::NOT; 68 | _earBigPressedTime = PressedTime::NOT; 69 | } 70 | } 71 | 72 | void BoxButtonEars::reloadConfig() { 73 | ConfigStruct* config = Config.get(); 74 | _earLongPressMs = config->buttonEars.longPressMs; 75 | _earVeryLongPressMs = config->buttonEars.veryLongPressMs; 76 | } 77 | 78 | void BoxButtonEars::waitForRelease() { 79 | while (true) { 80 | delay(100); 81 | Box.watchdog_feed(); 82 | _earSmall.read(); 83 | _earBig.read(); 84 | if (!_earSmall.isPressed() && !_earBig.isPressed()) 85 | break; 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /BoxButtonEars.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxButtonEars_h 2 | #define BoxButtonEars_h 3 | 4 | #include "BaseHeader.h" 5 | #include "JC_Button.h" 6 | 7 | #include 8 | 9 | class BoxButtonEars : public EnhancedThread { 10 | public: 11 | enum class PressedTime { NOT, SHORT, LONG, VERY_LONG }; 12 | enum class EarButton { NONE, SMALL, BIG, BOTH }; 13 | enum class PressedType { NONE, PRESS, RELEASE }; 14 | 15 | void 16 | begin(), 17 | loop(), 18 | reloadConfig(), 19 | waitForRelease(); 20 | 21 | private: 22 | 23 | uint16_t _earLongPressMs; 24 | uint16_t _earVeryLongPressMs; 25 | 26 | PressedTime _earSmallPressedTime = PressedTime::NOT; 27 | PressedTime _earBigPressedTime = PressedTime::NOT; 28 | PressedTime _earBothPressedTime = PressedTime::NOT; 29 | 30 | Button _earSmall = Button(59, 25, false, true); 31 | Button _earBig = Button(57, 25, false, true); 32 | }; 33 | 34 | #endif -------------------------------------------------------------------------------- /BoxCLI.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxCLI.h" 2 | 3 | #include "Hackiebox.h" 4 | #include "BoxEvents.h" 5 | #include 6 | 7 | void BoxCLI::begin() { 8 | setInterval(50); 9 | 10 | cmdI2C = cli.addCmd("i2c"); 11 | cmdI2C.setDescription(" Access I2C"); 12 | cmdI2C.addFlagArg("re/ad"); 13 | cmdI2C.addFlagArg("wr/ite"); 14 | cmdI2C.addArg("a/ddress"); 15 | cmdI2C.addArg("r/egister"); 16 | cmdI2C.addArg("v/alue", ""); 17 | cmdI2C.addArg("l/ength", "1"); 18 | cmdI2C.addArg("o/utput", "B"); 19 | 20 | cmdSpiRFID = cli.addCmd("spi-rfid"); 21 | cmdSpiRFID.setDescription(" Access RFID SPI"); 22 | cmdSpiRFID.addFlagArg("re/ad"); 23 | cmdSpiRFID.addFlagArg("wr/ite"); 24 | cmdSpiRFID.addFlagArg("c/md,co/mmand"); 25 | cmdSpiRFID.addArg("r/egister", "0"); 26 | cmdSpiRFID.addArg("v/alue", "0"); 27 | 28 | cmdBeep = cli.addCmd("beep"); 29 | cmdBeep.setDescription(" Beep with build-in DAC synthesizer"); 30 | cmdBeep.addArg("m/idi-id", "60"); 31 | cmdBeep.addArg("l/ength", "200"); 32 | 33 | cmdRFID = cli.addCmd("rfid"); 34 | cmdRFID.setDescription(" Access RFID"); 35 | cmdRFID.addFlagArg("u/id"); 36 | cmdRFID.addFlagArg("r/ead"); 37 | cmdRFID.addFlagArg("m/emory"); 38 | cmdRFID.addFlagArg("d/ump"); 39 | cmdRFID.addFlagArg("o/verwrite"); 40 | 41 | cmdLoad = cli.addCmd("load"); 42 | cmdLoad.setDescription(" Shows the load of all threads"); 43 | cmdLoad.addArg("n/ame", ""); 44 | cmdLoad.addArg("p/ointer", "0"); 45 | cmdLoad.addFlagArg("r/eset"); 46 | 47 | cmdHelp = cli.addSingleArgumentCommand("help"); 48 | cmdHelp.setDescription(" Show this screen"); 49 | 50 | cmdI2S = cli.addCmd("i2s"); 51 | cmdI2S.setDescription(" I2S debug information"); 52 | cmdI2S.addFlagArg("l/og"); 53 | cmdI2S.addArg("t/est", 0); 54 | cmdI2S.addArg("f/requency", "440"); 55 | 56 | cmdSay = cli.addCmd("say"); 57 | cmdSay.setDescription(" Generate speech with SAM"); 58 | cmdSay.addPosArg("t/ext"); 59 | cmdSay.addArg("v/oice", "0"); 60 | cmdSay.addArg("s/peed", "0"); 61 | cmdSay.addArg("p/itch", "0"); 62 | cmdSay.addArg("t/hroat", "0"); 63 | cmdSay.addArg("m/outh", "0"); 64 | cmdSay.addFlagArg("sing"); 65 | cmdSay.addFlagArg("p/hoentic"); 66 | 67 | cmdAudio = cli.addCmd("audio"); 68 | cmdAudio.setDescription(" Play/Pause audio files"); 69 | cmdAudio.addFlagArg("p/lay,pause"); 70 | cmdAudio.addArg("f/ile/name", ""); 71 | cmdAudio.addArg("g/en-limit", "0"); 72 | cmdAudio.addArg("b/uffer", "-1"); 73 | cmdAudio.addArg("m/axSampleRate", "0"); 74 | } 75 | 76 | void BoxCLI::loop() { 77 | parse(); 78 | } 79 | void BoxCLI::parse() { 80 | if (cli.available()) { 81 | lastCmd = cli.getCmd(); 82 | 83 | if (lastCmd == cmdHelp) { 84 | Log.println("Help:"); 85 | Log.println(); 86 | if (lastCmd.getArg(0).isSet()) { 87 | String arg = lastCmd.getArg(0).getValue(); 88 | Command cmd = cli.getCmd(arg); 89 | if (cmd.getName() == arg) { 90 | Log.println(cmd.toString().c_str()); 91 | return; 92 | } 93 | } 94 | 95 | Log.println(cli.toString().c_str()); 96 | } else if (lastCmd == cmdI2C) { 97 | execI2C(); 98 | } else if (lastCmd == cmdSpiRFID) { 99 | execSpiRFID(); 100 | } else if (lastCmd == cmdBeep) { 101 | execBeep(); 102 | } else if (lastCmd == cmdRFID) { 103 | execRFID(); 104 | } else if (lastCmd == cmdLoad) { 105 | execLoad(); 106 | } else if (lastCmd == cmdI2S) { 107 | execI2S(); 108 | } else if (lastCmd == cmdSay) { 109 | execSay(); 110 | } else if (lastCmd == cmdAudio) { 111 | execAudio(); 112 | } 113 | } 114 | 115 | if (cli.errored()) { 116 | CommandError cmdError = cli.getError(); 117 | 118 | Log.print("ERROR: "); 119 | Log.println(cmdError.toString().c_str()); 120 | 121 | if (cmdError.hasCommand()) { 122 | Log.print("Did you mean \""); 123 | Log.print(cmdError.getCommand().toString().c_str()); 124 | Log.println("\"?"); 125 | } 126 | } 127 | } 128 | 129 | void BoxCLI::execI2C() { 130 | Command c = lastCmd; 131 | //int argNum = c.countArgs(); 132 | 133 | unsigned long int tmpNum; 134 | uint8_t addr, regi; 135 | 136 | bool read = c.getArg("read").isSet(); 137 | bool write = c.getArg("write").isSet(); 138 | if (read == write) { 139 | Log.error("Either read or write has to be set"); 140 | return; 141 | } 142 | 143 | String saddress = c.getArg("address").getValue(); 144 | String sregister = c.getArg("register").getValue(); 145 | 146 | tmpNum = parseNumber(saddress); 147 | if (tmpNum > 127) { 148 | Log.error("address must be <128"); 149 | return; 150 | } 151 | addr = (uint8_t)tmpNum; 152 | 153 | tmpNum = parseNumber(sregister); 154 | if (tmpNum > 255) { 155 | Log.error("register must be <256"); 156 | return; 157 | } 158 | regi = (uint8_t)tmpNum; 159 | 160 | //Log.printfln("I2C command read=%T, write=%T, addr=%X, regi=%X", read, write, addr, regi); 161 | String slength = c.getArg("length").getValue(); 162 | String svalue = c.getArg("value").getValue(); 163 | if (read) { 164 | String soutput = c.getArg("output").getValue(); 165 | if (!(soutput == "x" || soutput == "X" || soutput == "i" || soutput == "b" || soutput == "B")) { 166 | Log.error("Allowed output values are: x, X, i, b, B"); 167 | return; 168 | } 169 | 170 | if (slength.length() == 0) { 171 | Log.error("length must be specified"); 172 | return; 173 | } 174 | tmpNum = parseNumber(slength); 175 | unsigned long len = tmpNum; 176 | 177 | Log.printfln("Read %i bytes", len); 178 | for (uint16_t i=0; i0) { 187 | while(isspace((unsigned char)*value)) value++; 188 | tmpNum = parseNumber(value, &newVal); 189 | if (tmpNum > 255) { 190 | Log.error("value must be <256"); 191 | return; 192 | } else if (value == newVal) { 193 | Log.error("Couldn't parse part \"%s\" of \"%s\"", value, svalue.c_str()); 194 | return; 195 | } 196 | uint8_t data = (uint8_t)tmpNum; 197 | bool result = Box.boxI2C.send(addr, regi++, data); 198 | if (!result) { 199 | Log.error(" %X not written, abort", data); 200 | break; 201 | } 202 | Log.info(" %X successful written", data); 203 | value = newVal; 204 | } 205 | } 206 | } 207 | 208 | void BoxCLI::execSpiRFID() { 209 | Command c = lastCmd; 210 | unsigned long tmpNum; 211 | 212 | String sregister = c.getArg("register").getValue(); 213 | tmpNum = parseNumber(sregister); 214 | if (tmpNum > 255) { 215 | Log.error("register must be <256"); 216 | return; 217 | } 218 | uint8_t regi = (uint8_t)tmpNum; 219 | 220 | String svalue = c.getArg("value").getValue(); 221 | tmpNum = parseNumber(svalue); 222 | if (tmpNum > 255) { 223 | Log.error("value/command must be <256"); 224 | return; 225 | } 226 | uint8_t value = (uint8_t)tmpNum; 227 | 228 | bool read = c.getArg("read").isSet(); 229 | bool write = c.getArg("write").isSet(); 230 | bool cmd = c.getArg("command").isSet(); 231 | 232 | //TODO Exclusive check 233 | 234 | if (read) { 235 | uint8_t result = Box.boxRFID.readRegister(regi); 236 | Log.info("Read from register=%X value=%X", regi, result); 237 | } else if (write) { 238 | Box.boxRFID.writeRegister(regi, value); 239 | Log.info("Write to register=%X value=%X", regi, value); 240 | } else if (cmd) { 241 | Box.boxRFID.sendCommand(value); 242 | Log.info("Send command %X", value); 243 | } 244 | } 245 | 246 | void BoxCLI::execBeep() { 247 | Command c = lastCmd; 248 | unsigned long tmpNum; 249 | 250 | String sid = c.getArg("midi-id").getValue(); 251 | tmpNum = parseNumber(sid); 252 | if (tmpNum > 127) { 253 | Log.error("midi-id must be <128"); 254 | return; 255 | } 256 | uint8_t id = (uint8_t)tmpNum; 257 | 258 | String slength = c.getArg("length").getValue(); 259 | tmpNum = parseNumber(slength); 260 | if (tmpNum > 65535) { 261 | Log.error("length must be <65.536"); 262 | return; 263 | } 264 | uint16_t length = (uint16_t)tmpNum; 265 | 266 | Box.boxDAC.beepMidi(id, length); 267 | } 268 | 269 | void BoxCLI::execRFID() { 270 | Command c = lastCmd; 271 | 272 | if (c.getArg("read").isSet()) { 273 | if (Box.boxRFID.tagActive) { 274 | Box.boxRFID.tagActive = false; 275 | Events.handleTagEvent(BoxRFID::TAG_EVENT::TAG_REMOVED); 276 | } 277 | Box.boxRFID.loop(); 278 | if (!Box.boxRFID.tagActive) { 279 | Log.error("No tag in place"); 280 | } 281 | } else if (c.getArg("uid").isSet()) { 282 | if (!Box.boxRFID.tagActive) { 283 | Log.error("No tag in place, last known is shown"); 284 | } 285 | Box.boxRFID.logUID(); 286 | } else if (c.getArg("memory").isSet()) { 287 | Box.boxRFID.loop(); 288 | if (!Box.boxRFID.tagActive) { 289 | Log.error("No tag in place"); 290 | } 291 | Box.boxRFID.logTagMemory(); 292 | } else if (c.getArg("dump").isSet()) { 293 | Box.boxRFID.loop(); 294 | if (!Box.boxRFID.tagActive) { 295 | Log.error("No tag in place"); 296 | } 297 | Box.boxRFID.dumpTagMemory(c.getArg("overwrite").isSet()); 298 | } else { 299 | Log.error("Nothing to do..."); 300 | } 301 | } 302 | 303 | void BoxCLI::execLoad() { 304 | Command c = lastCmd; 305 | 306 | String name = c.getArg("name").getValue(); 307 | String pointerStr = c.getArg("pointer").getValue(); 308 | bool reset = c.getArg("reset").isSet(); 309 | unsigned long pointer = parseNumber(pointerStr); 310 | 311 | if (name != "") { 312 | Log.info("Statistics for Threads starting with \"%s\"", name.c_str()); 313 | Log.println("---"); 314 | for (uint8_t i = 0; i < Box.threadController.size(); i++) { 315 | EnhancedThread* thread = (EnhancedThread*)Box.threadController.get(i); 316 | if (strncasecmp(name.c_str(), thread->ThreadName, name.length()) == 0) { 317 | thread->logStats(); 318 | if (reset) thread->resetStats(); 319 | Log.println(); 320 | } 321 | Box.delayTask(1); 322 | } 323 | } else if (pointer > 0) { 324 | Log.info("Statistics for Threads with pointer=%i", pointer); 325 | Log.println("---"); 326 | for (uint8_t i = 0; i < Box.threadController.size(); i++) { 327 | EnhancedThread* thread = (EnhancedThread*)Box.threadController.get(i); 328 | if (pointer == thread->ThreadID) { 329 | thread->logStats(); 330 | if (reset) thread->resetStats(); 331 | Log.println(); 332 | } 333 | } 334 | Box.delayTask(1); 335 | } else { 336 | Log.info("Statistics for all %i Threads", Box.threadController.size(false)); //TODO ThreadController 337 | Log.println("---"); 338 | for (uint8_t i = 0; i < Box.threadController.size(); i++) { 339 | EnhancedThread* thread = (EnhancedThread*)Box.threadController.get(i); 340 | thread->logStats(); 341 | if (reset) thread->resetStats(); 342 | Log.println(); 343 | Box.delayTask(1); 344 | } 345 | } 346 | 347 | if (reset) Log.info("All stats of selected threads reset."); 348 | } 349 | 350 | void BoxCLI::execI2S() { 351 | Command c = lastCmd; 352 | unsigned long freq = parseNumber(c.getArg("frequency").getValue()); 353 | unsigned long testTime = parseNumber(c.getArg("test").getValue()); 354 | if (c.getArg("log").isSet()) { 355 | Box.boxDAC.audioBuffer.logState(); 356 | Box.boxDAC.logDmaIrqChanges(); 357 | } 358 | if (testTime > 0 && freq > 0) { 359 | Log.info("Run frequency test for %ims with %iHz", testTime, freq); 360 | BoxTimer timer; 361 | timer.setTimer(testTime); 362 | while (timer.isRunning()) { 363 | Box.boxDAC.generateFrequency(freq, timer.getTimeTillEnd()); 364 | timer.tick(); 365 | } 366 | Box.boxDAC.audioOutput->flush(); 367 | 368 | if (c.getArg("log").isSet()) { 369 | Box.boxDAC.audioBuffer.logState(); 370 | Box.boxDAC.logDmaIrqChanges(); 371 | } 372 | } 373 | 374 | } 375 | 376 | void BoxCLI::execSay() { 377 | Command c = lastCmd; 378 | String text = c.getArg("text").getValue(); 379 | ESP8266SAM::SAMVoice voice = (ESP8266SAM::SAMVoice)parseNumber(c.getArg("voice").getValue()); 380 | uint8_t speed = parseNumber(c.getArg("speed").getValue()); 381 | uint8_t pitch = parseNumber(c.getArg("pitch").getValue()); 382 | uint8_t throat = parseNumber(c.getArg("throat").getValue()); 383 | uint8_t mouth = parseNumber(c.getArg("mouth").getValue()); 384 | bool sing = c.getArg("sing").isSet(); 385 | bool phoentic = c.getArg("phoentic").isSet(); 386 | 387 | if (text != "") { 388 | Box.boxDAC.samSay( 389 | text.c_str(), 390 | voice, 391 | speed, 392 | pitch, 393 | throat, 394 | mouth, 395 | sing, 396 | phoentic 397 | ); 398 | } 399 | } 400 | 401 | void BoxCLI::execAudio() { 402 | Command c = lastCmd; 403 | 404 | String filenameStr = c.getArg("file").getValue(); 405 | uint16_t genLimit = (uint16_t)parseNumber(c.getArg("gen-limit").getValue()); 406 | 407 | uint32_t resampleRate = (uint16_t)parseNumber(c.getArg("maxSampleRate").getValue()); 408 | String bufferStr = c.getArg("buffer").getValue(); 409 | 410 | int32_t buffer = -1; 411 | if (bufferStr != "-1") 412 | buffer = parseNumber(bufferStr); 413 | if (bufferStr != "0" && buffer == 0) 414 | buffer = -1; 415 | 416 | if (c.getArg("file").isSet() && filenameStr != "") { 417 | Box.boxDAC.playFile(filenameStr.c_str()); 418 | } else if (c.getArg("play").isSet()) { 419 | Box.boxDAC.audioPlaying = !Box.boxDAC.audioPlaying; 420 | } else { 421 | if (genLimit > 0) 422 | Box.boxDAC.audioTimeoutMs = genLimit; 423 | Log.info("Generator time limit is set to %ims", Box.boxDAC.audioTimeoutMs); 424 | 425 | if (resampleRate > 0) { 426 | Box.boxDAC.audioOutputResample->SetMaxRate(resampleRate); 427 | Log.info("Max Samplerate is set to %ihz", Box.boxDAC.audioOutputResample->GetMaxRate()); 428 | } 429 | if (buffer == 0) { 430 | Log.info("Additional buffering off"); 431 | Box.boxDAC.audioOutput = Box.boxDAC.audioOutputResample; 432 | } else if (buffer > 0) { 433 | if (buffer <= freeHeapMemory() / 2) { 434 | Box.boxDAC.audioOutput = Box.boxDAC.audioOutputBuffer; 435 | free(Box.boxDAC.audioOutputBuffer); 436 | Box.boxDAC.audioOutputBuffer = new AudioOutputBuffer(buffer, Box.boxDAC.audioOutputResample); 437 | Box.boxDAC.audioOutput = Box.boxDAC.audioOutputBuffer; 438 | Log.info("Additional buffer set to %ib", buffer); 439 | } else { 440 | Log.error("Buffer should not use more than half of the HEAP (%ib)", freeHeapMemory()); 441 | } 442 | } 443 | } 444 | } 445 | 446 | unsigned long BoxCLI::parseNumber(String numberString) { 447 | const char* num = numberString.c_str(); 448 | return parseNumber((char*)num); 449 | } 450 | unsigned long BoxCLI::parseNumber(char* number) { 451 | return parseNumber(number, NULL); 452 | } 453 | unsigned long BoxCLI::parseNumber(char* number, char** rest) { 454 | if (strncmp(number, "0x", 2) == 0) { 455 | return strtoul(number, rest, 16); 456 | } else if (strncmp(number, "0b", 2) == 0) { 457 | return strtoul(number+2, rest, 2); 458 | } 459 | return strtoul(number, rest, 10); 460 | } -------------------------------------------------------------------------------- /BoxCLI.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxCLI_h 2 | #define BoxCLI_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | #include 7 | 8 | class BoxCLI : public EnhancedThread { 9 | public: 10 | void 11 | begin(), 12 | loop(); 13 | 14 | SimpleCLI cli; 15 | 16 | private: 17 | Command lastCmd; 18 | 19 | Command cmdHelp; 20 | Command cmdI2C; 21 | Command cmdSpiRFID; 22 | Command cmdBeep; 23 | Command cmdRFID; 24 | Command cmdLoad; 25 | Command cmdI2S; 26 | Command cmdSay; 27 | Command cmdAudio; 28 | 29 | void parse(); 30 | unsigned long 31 | parseNumber(String number), 32 | parseNumber(char* number), 33 | parseNumber(char* number, char** rest); 34 | 35 | void 36 | execI2C(), 37 | execSpiRFID(), 38 | execBeep(), 39 | execRFID(), 40 | execLoad(), 41 | execI2S(), 42 | execSay(), 43 | execAudio(); 44 | }; 45 | 46 | #endif -------------------------------------------------------------------------------- /BoxConfig.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxConfig.h" 2 | 3 | #define CONFIG_SD_PATH "/revvox/hackiebox.config.json" 4 | 5 | void BoxConfig::begin() { 6 | read(); 7 | } 8 | 9 | void BoxConfig::read() { 10 | _initializeConfig(); 11 | FileFs file; 12 | if (file.open(CONFIG_SD_PATH, FA_OPEN_EXISTING | FA_READ)) { 13 | char buffer[4096]; 14 | size_t read; 15 | String json = String(); 16 | while (file.curPosition() < file.fileSize()) { 17 | read = file.read(buffer, sizeof(buffer)); 18 | if (read == 0) 19 | break; 20 | for (int i=0; i doc; 51 | doc["version"] = _config.version; 52 | 53 | JsonObject batteryDoc = doc.createNestedObject("battery"); 54 | ConfigBattery* batteryCfg = &_config.battery; 55 | batteryDoc["voltageFactor"] = batteryCfg->voltageFactor; 56 | batteryDoc["lowAdc"] = batteryCfg->lowAdc; 57 | batteryDoc["criticalAdc"] = batteryCfg->criticalAdc; 58 | batteryDoc["sleepMinutes"] = batteryCfg->sleepMinutes; 59 | 60 | JsonObject buttonsDoc = doc.createNestedObject("buttonEars"); 61 | ConfigButtonEars* buttonCfg = &_config.buttonEars; 62 | buttonsDoc["longPressMs"] = buttonCfg->longPressMs; 63 | buttonsDoc["veryLongPressMs"] = buttonCfg->veryLongPressMs; 64 | 65 | JsonObject wifiDoc = doc.createNestedObject("wifi"); 66 | ConfigWifi* wifiCfg = &_config.wifi; 67 | wifiDoc["ssid"] = wifiCfg->ssid; 68 | wifiDoc["password"] = wifiCfg->password; 69 | 70 | JsonObject logDoc = doc.createNestedObject("log"); 71 | ConfigLog* logCfg = &_config.log; 72 | logDoc["sdLog"] = logCfg->sdLog; 73 | 74 | JsonObject miscDoc = doc.createNestedObject("misc"); 75 | ConfigMisc* miscCfg = &_config.misc; 76 | miscDoc["autodump"] = miscCfg->autodump; 77 | miscDoc["swd"] = miscCfg->swd; 78 | miscDoc["watchdogSeconds"] = miscCfg->watchdogSeconds; 79 | 80 | _json = ""; 81 | serializeJson(doc, _json); 82 | return _json; 83 | } 84 | bool BoxConfig::setFromJson(String json) { 85 | StaticJsonDocument doc; 86 | 87 | DeserializationError err = deserializeJson(doc, json); 88 | if (err) { 89 | Log.error("deserializeJson() returned %s, %s", err.c_str(), json.c_str()); 90 | return false; 91 | } 92 | 93 | _config.version = doc["version"].as(); 94 | 95 | JsonObject batteryDoc = doc["battery"]; 96 | ConfigBattery* batteryCfg = &_config.battery; 97 | batteryCfg->voltageFactor = batteryDoc["voltageFactor"].as(); 98 | batteryCfg->lowAdc = batteryDoc["lowAdc"].as(); 99 | batteryCfg->criticalAdc = batteryDoc["criticalAdc"].as(); 100 | batteryCfg->sleepMinutes = batteryDoc["sleepMinutes"].as(); 101 | 102 | JsonObject buttonsDoc = doc["buttonEars"]; 103 | ConfigButtonEars* buttonCfg = &_config.buttonEars; 104 | buttonCfg->longPressMs = buttonsDoc["longPressMs"].as(); 105 | buttonCfg->veryLongPressMs = buttonsDoc["veryLongPressMs"].as(); 106 | 107 | JsonObject wifiDoc = doc["wifi"]; 108 | ConfigWifi* wifiCfg = &_config.wifi; 109 | strncpy(&wifiCfg->ssid[0], wifiDoc["ssid"].as(), sizeof(wifiCfg->ssid)); 110 | strncpy(&wifiCfg->password[0], wifiDoc["password"].as(), sizeof(wifiCfg->password)); 111 | 112 | JsonObject logDoc = doc["log"]; 113 | ConfigLog* logCfg = &_config.log; 114 | logCfg->sdLog = logDoc["sdLog"].as(); 115 | 116 | JsonObject miscDoc = doc["misc"]; 117 | ConfigMisc* miscCfg = &_config.misc; 118 | miscCfg->autodump = miscDoc["autodump"].as(); 119 | miscCfg->swd = miscDoc["swd"].as(); 120 | miscCfg->watchdogSeconds = miscDoc["watchdogSeconds"].as(); 121 | 122 | // Convert old config version to latest one. 123 | if (_config.version != CONFIG_ACTIVE_VERSION) { 124 | switch (_config.version) { 125 | case 2: 126 | batteryCfg->criticalAdc = batteryDoc["minimalAdc"].as(); 127 | batteryCfg->lowAdc = batteryCfg->criticalAdc + 100; 128 | _config.version = 3; 129 | case 3: 130 | miscCfg->autodump = false; 131 | _config.version = 4; 132 | case 4: 133 | miscCfg->swd = false; 134 | _config.version = 5; 135 | case 5: 136 | batteryCfg->lowAdc = 9658; 137 | batteryCfg->criticalAdc = 8869; 138 | batteryCfg->voltageFactor = 27850; 139 | _config.version = 6; 140 | case 6: 141 | miscCfg->watchdogSeconds = 10; 142 | if (batteryCfg->voltageFactor == 67690) //Fix for wrong value in previous CFW 143 | batteryCfg->voltageFactor = 27850; 144 | _config.version = 7; 145 | write(); 146 | break; 147 | default: 148 | _initializeConfig(); 149 | write(); 150 | break; 151 | } 152 | } 153 | 154 | return true; 155 | } 156 | 157 | void BoxConfig::_initializeConfig() { 158 | _config.version = CONFIG_ACTIVE_VERSION; 159 | 160 | //(4936,0258+0x59)/(10000/0x663d) = adc 161 | //(10000/0x663d)×13152−0x59 = v-OFW 162 | ConfigBattery* battery = &_config.battery; 163 | battery->voltageFactor = 27850; 164 | battery->lowAdc = 9658; //OFW 0xE11 (9657,837) 165 | battery->criticalAdc = 8869; //OFW 0xCE3/0xCE4 (8867,4124/8870,0297) 166 | battery->sleepMinutes = 15; 167 | 168 | ConfigButtonEars* buttons = &_config.buttonEars; 169 | buttons->longPressMs = 1000; 170 | buttons->veryLongPressMs = 10000; 171 | 172 | ConfigWifi* wifi = &_config.wifi; 173 | wifi->dhcp = true; 174 | #ifdef WIFI_SSID 175 | strcpy(wifi->ssid, WIFI_SSID); 176 | #endif 177 | #ifdef WIFI_PASS 178 | strcpy(wifi->password, WIFI_PASS); 179 | #endif 180 | 181 | ConfigLog* log = &_config.log; 182 | log->sdLog = false; 183 | 184 | ConfigMisc* misc = &_config.misc; 185 | misc->autodump = false; 186 | misc->swd = false; 187 | misc->watchdogSeconds = 10; 188 | } -------------------------------------------------------------------------------- /BoxConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxConfig_h 2 | #define BoxConfig_h 3 | 4 | #include "BaseHeader.h" 5 | #include "ConfigStructures.h" 6 | #include "BoxSD.h" 7 | 8 | 9 | #define BOXCONFIG_JSON_SIZE 560 10 | //{"version":255,"battery":{"voltageFactor":4294967295,"voltageChargerFactor":4294967295,"lowAdc":65535,"criticalAdc":65535,"sleepMinutes":255},"buttonEars":{"longPressMs":65535,"veryLongPressMs":65535},"wifi":{"ssid":"12345678901234567890123456789012","password":"1234567890123456789012345678901234567890123456789012345678901234"},"log":{"sdLog":false},"misc":{"autodump":false,"swd":false,"watchdogSeconds":255}} 11 | //Size from https://arduinojson.org/v6/assistant/ 12 | 13 | class BoxConfig { 14 | public: 15 | void 16 | begin(), 17 | read(), 18 | write(); 19 | 20 | String getAsJson(); 21 | bool setFromJson(String); 22 | 23 | ConfigStruct* get(); 24 | private: 25 | String _json; 26 | ConfigStruct _config; 27 | 28 | void _initializeConfig(); 29 | }; 30 | 31 | extern BoxConfig Config; 32 | 33 | #endif -------------------------------------------------------------------------------- /BoxEvents.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxEvents.h" 2 | 3 | void BoxEvents::begin() { 4 | 5 | } 6 | void BoxEvents::loop() { 7 | 8 | } 9 | 10 | void BoxEvents::handleEarEvent(BoxButtonEars::EarButton earId, BoxButtonEars::PressedType pressType, BoxButtonEars::PressedTime pressLength) { 11 | const char* nameEar; 12 | const char* nameType; 13 | const char* nameDuration; 14 | 15 | switch (earId) { 16 | case BoxButtonEars::EarButton::SMALL: 17 | nameEar = "Small ear"; 18 | break; 19 | case BoxButtonEars::EarButton::BIG: 20 | nameEar = "Big ear"; 21 | break; 22 | case BoxButtonEars::EarButton::BOTH: 23 | nameEar = "Both ears"; 24 | break; 25 | default: 26 | nameEar = "Unknown"; 27 | break; 28 | } 29 | 30 | switch (pressType) { 31 | case BoxButtonEars::PressedType::PRESS: 32 | nameType = "pressed"; 33 | break; 34 | case BoxButtonEars::PressedType::RELEASE: 35 | nameType = "released"; 36 | break; 37 | default: 38 | nameType = "?"; 39 | break; 40 | } 41 | 42 | switch (pressLength) { 43 | case BoxButtonEars::PressedTime::SHORT: 44 | nameDuration = "short"; 45 | break; 46 | case BoxButtonEars::PressedTime::LONG: 47 | nameDuration = "long"; 48 | break; 49 | case BoxButtonEars::PressedTime::VERY_LONG: 50 | nameDuration = "very long"; 51 | break; 52 | default: 53 | nameDuration = "unknown length"; 54 | break; 55 | } 56 | 57 | 58 | Log.info("%s %s-%s", nameEar, nameDuration, nameType); 59 | Box.boxPower.feedSleepTimer(); 60 | 61 | char earEvent[34]; 62 | snprintf(earEvent, 34, "{\"id\":%i,\"type\":%i,\"duration\":%i}", earId, pressType, pressLength); 63 | Box.webServer.sendEvent("Ear", earEvent, false); 64 | 65 | if (pressType == BoxButtonEars::PressedType::PRESS) { 66 | if (pressLength == BoxButtonEars::PressedTime::SHORT) { 67 | if (earId == BoxButtonEars::EarButton::BIG) { 68 | Box.boxDAC.increaseVolume(); 69 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 70 | Box.boxDAC.decreaseVolume(); 71 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 72 | 73 | } 74 | } else if (pressLength == BoxButtonEars::PressedTime::LONG) { 75 | if (earId == BoxButtonEars::EarButton::BIG) { 76 | if (Box.boxWiFi.getStatus() != WrapperWiFi::ConnectionState::CONNECTED) 77 | Box.boxWiFi.reconnect(); 78 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 79 | 80 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 81 | 82 | } 83 | } else if (pressLength == BoxButtonEars::PressedTime::VERY_LONG) { 84 | if (earId == BoxButtonEars::EarButton::BIG) { 85 | 86 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 87 | 88 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 89 | if (Box.boxAccel.getOrientation() == BoxAccelerometer::Orientation::EARS_UP) { 90 | Box.boxLEDs.setIdleAnimation(BoxLEDs::ANIMATION_TYPE::PULSE, BoxLEDs::CRGB::Blue); 91 | Box.boxWiFi.apMode(); 92 | } else if (Box.boxAccel.getOrientation() == BoxAccelerometer::Orientation::EARS_DOWN) { 93 | Box.boxPower.reset(); 94 | } else if (Box.boxAccel.getOrientation() == BoxAccelerometer::Orientation::EARS_FRONT) { 95 | //Prepare Hibernation 96 | Box.boxLEDs.setAllBool(false); 97 | Box.boxEars.waitForRelease(); 98 | delay(500); 99 | Box.boxPower.hibernate(); 100 | } 101 | } 102 | } 103 | } else if (pressType == BoxButtonEars::PressedType::RELEASE) { 104 | if (pressLength == BoxButtonEars::PressedTime::SHORT) { 105 | if (earId == BoxButtonEars::EarButton::BIG) { 106 | 107 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 108 | 109 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 110 | 111 | } 112 | } else if (pressLength == BoxButtonEars::PressedTime::LONG) { 113 | if (earId == BoxButtonEars::EarButton::BIG) { 114 | if (Box.boxWiFi.getStatus() != WrapperWiFi::ConnectionState::CONNECTED) 115 | Box.boxWiFi.reconnect(); 116 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 117 | 118 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 119 | 120 | } 121 | } else if (pressLength == BoxButtonEars::PressedTime::VERY_LONG) { 122 | if (earId == BoxButtonEars::EarButton::BIG) { 123 | 124 | } else if (earId == BoxButtonEars::EarButton::SMALL) { 125 | 126 | } else if (earId == BoxButtonEars::EarButton::BOTH) { 127 | 128 | } 129 | } 130 | } 131 | } 132 | 133 | void BoxEvents::handleBatteryEvent(BoxBattery::BatteryEvent state) { 134 | char batteryEvent[12]; 135 | snprintf(batteryEvent, 12, "{\"state\":%i}", state); 136 | Box.webServer.sendEvent("Battery", batteryEvent, false); 137 | 138 | switch (state) { 139 | case BoxBattery::BatteryEvent::BAT_CRITICAL: 140 | Log.info("Battery is critical, connect the charger, hibernating!"); 141 | Box.boxBattery.stopBatteryTest(); 142 | Box.boxBattery.logBatteryStatus(); 143 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Orange, 3); 144 | Box.boxLEDs.waitForAnimationToFinish(); 145 | Box.boxPower.hibernate(); 146 | break; 147 | case BoxBattery::BatteryEvent::BAT_LOW: 148 | Log.info("Battery is low, connect the charger!"); 149 | Box.boxBattery.logBatteryStatus(); 150 | Box.boxLEDs.setIdleAnimation(BoxLEDs::ANIMATION_TYPE::PULSE, BoxLEDs::CRGB::Orange); 151 | break; 152 | case BoxBattery::BatteryEvent::CHR_CONNECT: 153 | Log.info("Charger connected"); 154 | Box.boxBattery.logBatteryStatus(); 155 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::White, 3); 156 | break; 157 | case BoxBattery::BatteryEvent::CHR_DISCONNECT: 158 | Log.info("Charger disconnected"); 159 | Box.boxBattery.logBatteryStatus(); 160 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::DarkSlateGray, 3); 161 | break; 162 | } 163 | } 164 | 165 | void BoxEvents::handleWiFiEvent(WrapperWiFi::ConnectionState state) { 166 | switch (state) { 167 | case WrapperWiFi::ConnectionState::WAIT_CONNECT: 168 | break; 169 | case WrapperWiFi::ConnectionState::WAIT_IP: 170 | Log.info("WiFi connected, waiting for ip..."); 171 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Cyan, 3); 172 | break; 173 | case WrapperWiFi::ConnectionState::CONNECTED: 174 | Log.info("IP: %s", WiFi.localIP().toString().c_str()); 175 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Blue, 3); 176 | Box.boxWiFi.mDnsAdvertiseSetup(); 177 | break; 178 | case WrapperWiFi::ConnectionState::DISCONNECTED: 179 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Red, 3); 180 | Log.info("WiFi lost"); 181 | break; 182 | default: 183 | break; 184 | } 185 | } 186 | 187 | void BoxEvents::handlePowerEvent(BoxPower::PowerEvent event) { 188 | switch (event) 189 | { 190 | case BoxPower::PowerEvent::PRE_HIBERNATE: 191 | Log.info("Go into hibernation..."); 192 | break; 193 | case BoxPower::PowerEvent::PRE_RESET: 194 | Log.info("Reset box..."); 195 | break; 196 | case BoxPower::PowerEvent::BOX_IDLE: 197 | if (Box.boxBattery.batteryTestActive()) { 198 | Log.info("Box unused, battery test running, keep alive..."); 199 | Box.boxPower.feedSleepTimer(); 200 | return; 201 | } 202 | Log.info("Box unused, power off."); 203 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Green, 3); 204 | Box.boxLEDs.waitForAnimationToFinish(); 205 | Box.boxPower.hibernate(); 206 | break; 207 | 208 | default: 209 | break; 210 | } 211 | } 212 | 213 | void BoxEvents::handleAccelerometerOrientationEvent(BoxAccelerometer::Orientation orient) { 214 | const char* orientText; 215 | switch (orient) { 216 | case BoxAccelerometer::Orientation::EARS_UP: 217 | orientText = "ears up"; 218 | Box.boxDAC.play(); 219 | break; 220 | case BoxAccelerometer::Orientation::EARS_DOWN: 221 | orientText = "ears down"; 222 | break; 223 | case BoxAccelerometer::Orientation::EARS_FRONT: 224 | orientText = "ears front"; 225 | Box.boxDAC.pause(); 226 | break; 227 | case BoxAccelerometer::Orientation::EARS_BACK: 228 | orientText = "ears back"; 229 | break; 230 | case BoxAccelerometer::Orientation::EARS_LEFT: 231 | orientText = "ears left"; 232 | break; 233 | case BoxAccelerometer::Orientation::EARS_RIGHT: 234 | orientText = "ears right"; 235 | break; 236 | case BoxAccelerometer::Orientation::OTHER: 237 | orientText = "lockout"; 238 | break; 239 | 240 | default: 241 | orientText = "unknown"; 242 | Log.error("Unknown orientation=%i", orient); 243 | break; 244 | } 245 | Log.info("Box' orientation changed to %s", orientText); 246 | 247 | char orientEvent[9]; 248 | snprintf(orientEvent, 9, "{\"id\":%i}", orient); 249 | Box.webServer.sendEvent("Orientation", orientEvent, false); 250 | 251 | Box.boxPower.feedSleepTimer(); 252 | } 253 | 254 | void BoxEvents::handleTagEvent(BoxRFID::TAG_EVENT event) { 255 | uint8_t uid[24]; 256 | uid[0] = '\0'; 257 | 258 | switch (event) { 259 | case BoxRFID::TAG_EVENT::TAG_PLACED: 260 | Log.info("Tag placed", event); 261 | Box.boxLEDs.setIdleAnimation(BoxLEDs::ANIMATION_TYPE::PARTY, BoxLEDs::CRGB::White); 262 | Box.boxRFID.logUID(); 263 | Box.boxRFID.getUID(uid); 264 | 265 | if (!Box.boxDAC.hasStopped() && (memcmp(Box.boxRFID.tagUid, Box.boxTonie.currentUid, 8) == 0)) { 266 | Log.info("Continue playing last file"); 267 | Box.boxPlayer.play(); 268 | } else { 269 | DirFs dir; 270 | if(Config.get()->misc.autodump) { 271 | Log.info("Autodump..."); 272 | const char* rdump = "/rDUMP"; 273 | if (!dir.openDir(rdump)) { 274 | Log.info("Create dir %s...", rdump); 275 | if (!FatFs.mkdir(rdump)) { 276 | Log.info("...fail!"); 277 | } 278 | } 279 | if (Box.boxRFID.dumpTagMemory(false)) { 280 | Box.boxLEDs.setActiveAnimationByIteration(BoxLEDs::ANIMATION_TYPE::BLINK, BoxLEDs::CRGB::Yellow, 2); 281 | Box.boxDAC.beepMidi(84, 100, false); 282 | Box.boxDAC.beepMidi(76, 100, false); 283 | } else { 284 | } 285 | } else { 286 | Log.info("No Autodump"); 287 | } 288 | 289 | const char* rcontent = "/rCONTENT"; 290 | if (!dir.openDir(rcontent)) { 291 | Log.info("Create dir %s...", rcontent); 292 | if (!FatFs.mkdir(rcontent)) { 293 | Log.info("...fail!"); 294 | } 295 | } 296 | 297 | Box.boxTonie.loadTonieByUid(Box.boxRFID.tagUid); 298 | uint8_t path[strlen(Box.boxTonie.RCONTENT_BASE)+16+1]; 299 | uint8_t* uid = Box.boxRFID.tagUid; 300 | sprintf( 301 | (char*)path, 302 | "%s%02X%02X%02X%02X%02X%02X%02X%02X", 303 | Box.boxTonie.RCONTENT_BASE, 304 | uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7] 305 | ); 306 | if (!dir.openDir((char*)path)) { 307 | Log.info("Create dir %s...", path); 308 | if (!FatFs.mkdir((char*)path)) { 309 | Log.info("...fail!"); 310 | } 311 | } else { 312 | Box.boxPlayer.playDir((const char*)path, BoxPlayer::PLAYER_FLAGS::NONE); 313 | } 314 | } 315 | break; 316 | case BoxRFID::TAG_EVENT::TAG_REMOVED: 317 | Log.info("Tag removed", event); 318 | Box.boxLEDs.defaultIdleAnimation(); 319 | Box.boxDAC.pause(); 320 | break; 321 | default: 322 | Log.error("Unknown TAG_EVENT=%X", event); 323 | break; 324 | } 325 | 326 | 327 | char tagEvent[44]; 328 | snprintf(tagEvent, 44, "{\"event\":%i,\"uid\":\"%s\"}", event, uid, false); 329 | Box.webServer.sendEvent("Tag", tagEvent, false); 330 | 331 | Box.boxPower.feedSleepTimer(); 332 | } 333 | 334 | void BoxEvents::handleHeadphoneEvent(BoxDAC::HeadphoneEvent event) { 335 | switch (event){ 336 | case BoxDAC::HeadphoneEvent::INSERTED: 337 | Log.info("Headphones connected"); 338 | Box.boxDAC.muteSpeaker(true); 339 | Box.boxDAC.muteHeadphones(false); 340 | break; 341 | case BoxDAC::HeadphoneEvent::REMOVED: 342 | Log.info("Headphones disconnected"); 343 | Box.boxDAC.muteHeadphones(true); 344 | Box.boxDAC.muteSpeaker(false); 345 | break; 346 | } 347 | char hpEvent[12]; 348 | snprintf(hpEvent, 12, "{\"event\":%i}", hpEvent); 349 | Box.webServer.sendEvent("Headphones", hpEvent, false); 350 | } -------------------------------------------------------------------------------- /BoxEvents.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxEvents_h 2 | #define BoxEvents_h 3 | 4 | #include "BaseHeader.h" 5 | #include "Hackiebox.h" 6 | #include "WrapperWiFi.h" 7 | #include "BoxPower.h" 8 | #include "BoxRFID.h" 9 | 10 | #include "BoxTonies.h" 11 | 12 | class BoxEvents { 13 | public: 14 | enum class EventSource { 15 | BATTERY, 16 | EAR, 17 | WIFI, 18 | POWER, 19 | ACCELEROMETER, 20 | TAG, 21 | HEADPHONE 22 | }; 23 | 24 | void 25 | begin(), 26 | loop(); 27 | 28 | void handleEarEvent(BoxButtonEars::EarButton earId, BoxButtonEars::PressedType pressType, BoxButtonEars::PressedTime pressLength); 29 | void handleBatteryEvent(BoxBattery::BatteryEvent state); 30 | void handleWiFiEvent(WrapperWiFi::ConnectionState state); 31 | void handlePowerEvent(BoxPower::PowerEvent event); 32 | void handleAccelerometerOrientationEvent(BoxAccelerometer::Orientation orient); 33 | void handleTagEvent(BoxRFID::TAG_EVENT event); 34 | void handleHeadphoneEvent(BoxDAC::HeadphoneEvent event); 35 | 36 | private: 37 | }; 38 | 39 | extern BoxEvents Events; 40 | 41 | #endif -------------------------------------------------------------------------------- /BoxI2C.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxI2C.h" 2 | 3 | void BoxI2C::begin() { 4 | //Wire.begin(); //TODO! 5 | } 6 | 7 | bool BoxI2C::send_raw(uint8_t data) { 8 | if (!Wire.write(data)) { 9 | Log.error("Couldn't write into I2C Buffer"); 10 | return false; 11 | } 12 | return true; 13 | } 14 | bool BoxI2C::send(uint8_t address, uint8_t target_register, uint8_t data) { 15 | Wire.beginTransmission(address); 16 | if (!send_raw(target_register)) return false; 17 | if (!send_raw(data)) return false; 18 | 19 | uint8_t result = Wire.endTransmission(false); 20 | if (!result) return true; 21 | Log.error("Couldn't send I2C buffer, error=%i", result); 22 | return false; 23 | } 24 | 25 | uint8_t BoxI2C::readByte(uint8_t address, uint8_t source_register) { 26 | Wire.beginTransmission(0x18); 27 | if (!send_raw(source_register)) return false; 28 | Wire.endTransmission(false); 29 | if (!Wire.requestFrom(0x18 ,1)) return false; 30 | int result = Wire.read(); 31 | //Log.info("readI2C addr=%i reg=%i result=%i", address, source_register, result); 32 | if (result == -1) return false; 33 | return (uint8_t)result; 34 | } -------------------------------------------------------------------------------- /BoxI2C.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxI2C_h 2 | #define BoxI2C_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | 7 | class BoxI2C { 8 | public: 9 | void 10 | begin(); 11 | 12 | bool send(uint8_t address, uint8_t target_register, uint8_t data); 13 | bool send_raw(uint8_t data); 14 | uint8_t readByte(uint8_t address, uint8_t source_register); 15 | }; 16 | 17 | #endif -------------------------------------------------------------------------------- /BoxLEDs.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxLEDs.h" 2 | #include "wiring_private.h" 3 | #include "Hackiebox.h" 4 | 5 | void BoxLEDs::begin(bool swd) { 6 | disableRedLED(swd); //PWMPrepare(PIN_RED); 7 | if (swd) 8 | Log.info("Keep red LED inactive to enable SWD"); 9 | 10 | PWMPrepare(PIN_GREEN); 11 | PWMPrepare(PIN_BLUE); 12 | 13 | _stateRed = LED_PWM_MIN; 14 | _stateGreen = LED_PWM_MIN; 15 | _stateBlue = LED_PWM_MIN; 16 | } 17 | void BoxLEDs::defaultIdleAnimation() { 18 | Box.boxLEDs.setIdleAnimation(BoxLEDs::ANIMATION_TYPE::RAINBOW, BoxLEDs::CRGB::White); 19 | } 20 | 21 | void BoxLEDs::loop() { 22 | _timer.tick(); 23 | if (_timer.isRunning()) { 24 | _handleAnimation(&_activeAnimation); 25 | } else { 26 | _handleAnimation(&_idleAnimation); 27 | } 28 | } 29 | 30 | void BoxLEDs::_handleAnimation(ANIMATION* animation) { 31 | if (_timer.wasRunning()) 32 | setIntervalForAnimationType(animation->type); 33 | 34 | if (animation->type == ANIMATION_TYPE::RAINBOW) { 35 | setAll(_wheel(animation->state)); 36 | if (animation->state < 255) { 37 | animation->state++; 38 | } else { 39 | animation->state = 0; 40 | } 41 | } else if (animation->type == ANIMATION_TYPE::PARTY) { 42 | setAll(_wheel(random(255))); 43 | } else if (animation->type == ANIMATION_TYPE::PULSE) { 44 | if (animation->direction == ANIMATION_DIRECTION::UP) { 45 | if (animation->state <= 0xFF - animation->step) { 46 | animation->state += animation->step; 47 | } else { 48 | animation->direction = ANIMATION_DIRECTION::DOWN; 49 | animation->state = 0xFF; 50 | } 51 | } else { 52 | if (animation->state >= 0x00 + animation->step) { 53 | animation->state -= animation->step; 54 | } else { 55 | animation->direction = ANIMATION_DIRECTION::UP; 56 | animation->state = 0x00; 57 | } 58 | } 59 | setAll(_transformPulse(animation->state, animation->color)); 60 | } else if (animation->type == ANIMATION_TYPE::SOLID) { 61 | setAll(animation->color); 62 | } else if (animation->type == ANIMATION_TYPE::BLINK) { 63 | if (animation->direction == ANIMATION_DIRECTION::UP) { 64 | setAll(animation->color); 65 | animation->direction = ANIMATION_DIRECTION::DOWN; 66 | } else { 67 | setAll(0,0,0); 68 | animation->direction = ANIMATION_DIRECTION::UP; 69 | } 70 | } 71 | } 72 | 73 | void BoxLEDs::disableRedLED(bool disabled) { 74 | if (_redLedDisabled == disabled) 75 | return; 76 | 77 | if (disabled) { 78 | MAP_PinModeSet(PIN_19, PIN_MODE_1); //TCK 79 | MAP_PinModeSet(PIN_20, PIN_MODE_1); //TMS 80 | } else { 81 | PWMPrepare(PIN_RED); 82 | } 83 | _redLedDisabled = disabled; 84 | } 85 | 86 | unsigned long BoxLEDs::getDurationByIterations(uint8_t iterations, ANIMATION_TYPE animationType) { 87 | unsigned long animationInterval = getIntervalForAnimationType(animationType); 88 | switch (animationType) { 89 | case ANIMATION_TYPE::RAINBOW: 90 | return iterations * animationInterval * 255; 91 | break; 92 | case ANIMATION_TYPE::PARTY: 93 | return iterations * animationInterval * 1; 94 | break; 95 | case ANIMATION_TYPE::PULSE: 96 | return iterations * animationInterval * 255 * 2 / 5; //generalize steps 97 | break; 98 | case ANIMATION_TYPE::BLINK: 99 | return iterations * animationInterval * 2; 100 | break; 101 | default: 102 | break; 103 | } 104 | return 0; 105 | } 106 | void BoxLEDs::setIdleAnimation(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor) { 107 | setAnimation(&_idleAnimation, animationType, animationColor); 108 | } 109 | 110 | void BoxLEDs::setActiveAnimationByDuration(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor, unsigned long duration) { 111 | setAnimation(&_activeAnimation, animationType, animationColor); 112 | _timer.setTimer(duration); 113 | } 114 | void BoxLEDs::setActiveAnimationByIteration(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor, uint8_t iterations) { 115 | setActiveAnimationByDuration(animationType, animationColor, getDurationByIterations(iterations, animationType)); 116 | } 117 | 118 | void BoxLEDs::setAnimation(ANIMATION* animation, ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor) { 119 | animation->type = animationType; 120 | animation->color = animationColor; 121 | animation->state = 0; 122 | animation->step = 1; 123 | animation->direction = ANIMATION_DIRECTION::UP; 124 | setIntervalForAnimationType(animation->type); 125 | 126 | switch (animationType) { 127 | case ANIMATION_TYPE::RAINBOW: 128 | break; 129 | case ANIMATION_TYPE::BLINK: 130 | animation->direction = ANIMATION_DIRECTION::UP; 131 | break; 132 | case ANIMATION_TYPE::PARTY: 133 | break; 134 | case ANIMATION_TYPE::PULSE: 135 | animation->step = 5; 136 | animation->direction = ANIMATION_DIRECTION::UP; 137 | break; 138 | case ANIMATION_TYPE::SOLID: 139 | break; 140 | 141 | default: 142 | break; 143 | } 144 | } 145 | 146 | unsigned long BoxLEDs::getIntervalForAnimationType(ANIMATION_TYPE idleType) { 147 | switch (idleType) { 148 | case ANIMATION_TYPE::RAINBOW: 149 | return 100; 150 | break; 151 | case ANIMATION_TYPE::PARTY: 152 | return 250; 153 | break; 154 | case ANIMATION_TYPE::PULSE: 155 | return 30; 156 | break; 157 | case ANIMATION_TYPE::SOLID: 158 | return 250; 159 | break; 160 | case ANIMATION_TYPE::BLINK: 161 | return 350; 162 | break; 163 | } 164 | return 0; 165 | } 166 | void BoxLEDs::setIntervalForAnimationType(ANIMATION_TYPE idleType) { 167 | setInterval(getIntervalForAnimationType(idleType)); 168 | } 169 | bool BoxLEDs::hasActiveAnimation() { 170 | return _timer.isRunning(); 171 | } 172 | void BoxLEDs::waitForAnimationToFinish() { 173 | while (hasActiveAnimation()) { 174 | runIfNeeded(); 175 | Box.watchdog_feed(); 176 | Box.delayTask(1); 177 | } 178 | } 179 | 180 | void BoxLEDs::setRed(uint8_t intensity) { 181 | uint8_t currentState = _stateRed; 182 | if (intensity < LED_PWM_MIN) { 183 | _stateRed = LED_PWM_MIN; 184 | } else if (intensity > LED_PWM_MAX) { 185 | _stateRed = LED_PWM_MAX; 186 | } else { 187 | _stateRed = intensity; 188 | } 189 | 190 | if ((currentState != _stateRed) && !_redLedDisabled) { 191 | analogWrite(PIN_RED, _stateRed); 192 | } 193 | } 194 | 195 | void BoxLEDs::setGreen(uint8_t intensity) { 196 | uint8_t currentState = _stateGreen; 197 | if (intensity < LED_PWM_MIN) { 198 | _stateGreen = LED_PWM_MIN; 199 | } else if (intensity > LED_PWM_MAX) { 200 | _stateGreen = LED_PWM_MAX; 201 | } else { 202 | _stateGreen = intensity; 203 | } 204 | 205 | if (currentState != _stateGreen) { 206 | analogWrite(PIN_GREEN, _stateGreen); 207 | } 208 | } 209 | 210 | void BoxLEDs::setBlue(uint8_t intensity) { 211 | uint8_t currentState = _stateBlue; 212 | if (intensity < LED_PWM_MIN) { 213 | _stateBlue = LED_PWM_MIN; 214 | } else if (intensity > LED_PWM_MAX) { 215 | _stateBlue = LED_PWM_MAX; 216 | } else { 217 | _stateBlue = intensity; 218 | } 219 | 220 | if (currentState != _stateBlue) { 221 | analogWrite(PIN_BLUE, _stateBlue); 222 | } 223 | } 224 | 225 | void BoxLEDs::setRedBool(bool enabled) { 226 | uint8_t currentState = _stateRed; 227 | if (enabled) { 228 | _stateRed = LED_PWM_MAX; 229 | } else { 230 | _stateRed = LED_PWM_MIN; 231 | } 232 | if ((currentState != _stateRed) && !_redLedDisabled) { 233 | analogWrite(PIN_RED, _stateRed); 234 | } 235 | } 236 | 237 | void BoxLEDs::setGreenBool(bool enabled) { 238 | uint8_t currentState = _stateGreen; 239 | if (enabled) { 240 | _stateGreen = LED_PWM_MAX; 241 | } else { 242 | _stateGreen = LED_PWM_MIN; 243 | } 244 | 245 | if (currentState != _stateGreen) { 246 | analogWrite(PIN_GREEN, _stateGreen); 247 | } 248 | } 249 | 250 | void BoxLEDs::setBlueBool(bool enabled) { 251 | uint8_t currentState = _stateBlue; 252 | if (enabled) { 253 | _stateBlue = LED_PWM_MAX; 254 | } else { 255 | _stateBlue = LED_PWM_MIN; 256 | } 257 | 258 | if (currentState != _stateBlue) { 259 | analogWrite(PIN_BLUE, _stateBlue); 260 | } 261 | } 262 | 263 | uint8_t BoxLEDs::getRed() { 264 | return _stateRed; 265 | } 266 | 267 | uint8_t BoxLEDs::getGreen() { 268 | return _stateGreen; 269 | } 270 | 271 | uint8_t BoxLEDs::getBlue() { 272 | return _stateBlue; 273 | } 274 | 275 | void BoxLEDs::setAllBool(bool enabled) { 276 | setAllBool(enabled, enabled, enabled); 277 | } 278 | 279 | void BoxLEDs::setAllBool(bool red, bool green, bool blue) { 280 | setRedBool(red); 281 | setGreenBool(green); 282 | setBlueBool(blue); 283 | } 284 | 285 | void BoxLEDs::setWhite(uint8_t intensity) { 286 | setAll(intensity, intensity, intensity); 287 | } 288 | 289 | void BoxLEDs::setAll(uint8_t red, uint8_t green, uint8_t blue) { 290 | setRed(red); 291 | setGreen(green); 292 | setBlue(blue); 293 | } 294 | 295 | void BoxLEDs::setAll(CRGB crgb) { 296 | setAll(crgb.red, crgb.green, crgb.blue); 297 | } 298 | 299 | void BoxLEDs::setAll(uint32_t color) { 300 | CRGB crgb; 301 | crgb.setRGB(color); 302 | setAll(crgb); 303 | } 304 | 305 | void BoxLEDs::testLEDs() { 306 | uint8_t ledR = getRed(); 307 | uint8_t ledG = getGreen(); 308 | uint8_t ledB = getBlue(); 309 | 310 | Log.info("Test LEDs..."); 311 | Box.delayTask(250); 312 | 313 | Log.info(" Red"); 314 | setAll(CRGB::Red); 315 | Box.delayTask(250); 316 | setAll(0x7F, 0x00, 0x00); 317 | Box.delayTask(250); 318 | Log.info(" Green"); 319 | setAll(CRGB::Green); 320 | Box.delayTask(250); 321 | setAll(0x00, 0x7F, 0x00); 322 | Box.delayTask(250); 323 | Log.info(" Blue"); 324 | setAll(CRGB::Blue); 325 | Box.delayTask(250); 326 | setAll(0x00, 0x00, 0x7F); 327 | Box.delayTask(250); 328 | Log.info(" RGB"); 329 | setAll(CRGB::White); 330 | Box.delayTask(250); 331 | setAll(0x7F, 0x7F, 0x7F); 332 | Box.delayTask(250); 333 | 334 | Log.info(" Off"); 335 | setAll(CRGB::Black); 336 | 337 | Box.delayTask(500); 338 | Log.info(" Reset"); 339 | setAll(ledR, ledG, ledB); 340 | Log.info(" finished"); 341 | } 342 | 343 | BoxLEDs::CRGB BoxLEDs::_wheel(uint8_t wheelPos) { 344 | CRGB color; 345 | if (wheelPos < 85) { 346 | color.setRGB(wheelPos * 3, 255 - wheelPos * 3, 0); 347 | } else if (wheelPos < 170) { 348 | wheelPos -= 85; 349 | color.setRGB(255 - wheelPos * 3, 0, wheelPos * 3); 350 | } else { 351 | wheelPos -= 170; 352 | color.setRGB(0, wheelPos * 3, 255 - wheelPos * 3); 353 | } 354 | return color; 355 | } 356 | 357 | BoxLEDs::CRGB BoxLEDs::_transformPulse(uint8_t state, CRGB originalColor) { 358 | CRGB color; 359 | color.setRGB(originalColor.red*state/255, originalColor.blue*state/255, originalColor.green*state/255); 360 | return color; 361 | } 362 | -------------------------------------------------------------------------------- /BoxLEDs.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxLEDs_h 2 | #define BoxLEDs_h 3 | 4 | #include "BaseHeader.h" 5 | #include "BoxTimer.h" 6 | #include 7 | 8 | class BoxLEDs : public EnhancedThread { 9 | public: 10 | enum class ANIMATION_TYPE { 11 | SOLID, 12 | PULSE, 13 | BLINK, 14 | RAINBOW, 15 | PARTY, 16 | }; 17 | enum class ANIMATION_DIRECTION { 18 | UP, 19 | DOWN, 20 | }; 21 | struct CRGB { 22 | uint8_t red, green, blue; 23 | 24 | void setRGB(uint8_t r, uint8_t g, uint8_t b) { 25 | red = r; 26 | green = g; 27 | blue = b; 28 | } 29 | void setRGB(uint32_t colorcode) { 30 | red = (colorcode >> 16) & 0xFF; 31 | green = (colorcode >> 8) & 0xFF; 32 | blue = (colorcode >> 0) & 0xFF; 33 | } 34 | inline CRGB& operator= (const uint32_t colorcode) __attribute__((always_inline)) { 35 | red = (colorcode >> 16) & 0xFF; 36 | green = (colorcode >> 8) & 0xFF; 37 | blue = (colorcode >> 0) & 0xFF; 38 | return *this; 39 | } 40 | /// Predefined RGB colors 41 | typedef enum { 42 | AliceBlue=0xF0F8FF, 43 | Amethyst=0x9966CC, 44 | AntiqueWhite=0xFAEBD7, 45 | Aqua=0x00FFFF, 46 | Aquamarine=0x7FFFD4, 47 | Azure=0xF0FFFF, 48 | Beige=0xF5F5DC, 49 | Bisque=0xFFE4C4, 50 | Black=0x000000, 51 | BlanchedAlmond=0xFFEBCD, 52 | Blue=0x0000FF, 53 | BlueViolet=0x8A2BE2, 54 | Brown=0xA52A2A, 55 | BurlyWood=0xDEB887, 56 | CadetBlue=0x5F9EA0, 57 | Chartreuse=0x7FFF00, 58 | Chocolate=0xD2691E, 59 | Coral=0xFF7F50, 60 | CornflowerBlue=0x6495ED, 61 | Cornsilk=0xFFF8DC, 62 | Crimson=0xDC143C, 63 | Cyan=0x00FFFF, 64 | DarkBlue=0x00008B, 65 | DarkCyan=0x008B8B, 66 | DarkGoldenrod=0xB8860B, 67 | DarkGray=0xA9A9A9, 68 | DarkGrey=0xA9A9A9, 69 | DarkGreen=0x006400, 70 | DarkKhaki=0xBDB76B, 71 | DarkMagenta=0x8B008B, 72 | DarkOliveGreen=0x556B2F, 73 | DarkOrange=0xFF8C00, 74 | DarkOrchid=0x9932CC, 75 | DarkRed=0x8B0000, 76 | DarkSalmon=0xE9967A, 77 | DarkSeaGreen=0x8FBC8F, 78 | DarkSlateBlue=0x483D8B, 79 | DarkSlateGray=0x2F4F4F, 80 | DarkSlateGrey=0x2F4F4F, 81 | DarkTurquoise=0x00CED1, 82 | DarkViolet=0x9400D3, 83 | DeepPink=0xFF1493, 84 | DeepSkyBlue=0x00BFFF, 85 | DimGray=0x696969, 86 | DimGrey=0x696969, 87 | DodgerBlue=0x1E90FF, 88 | FireBrick=0xB22222, 89 | FloralWhite=0xFFFAF0, 90 | ForestGreen=0x228B22, 91 | Fuchsia=0xFF00FF, 92 | Gainsboro=0xDCDCDC, 93 | GhostWhite=0xF8F8FF, 94 | Gold=0xFFD700, 95 | Goldenrod=0xDAA520, 96 | Gray=0x808080, 97 | Grey=0x808080, 98 | Green=0x008000, 99 | GreenYellow=0xADFF2F, 100 | Honeydew=0xF0FFF0, 101 | HotPink=0xFF69B4, 102 | IndianRed=0xCD5C5C, 103 | Indigo=0x4B0082, 104 | Ivory=0xFFFFF0, 105 | Khaki=0xF0E68C, 106 | Lavender=0xE6E6FA, 107 | LavenderBlush=0xFFF0F5, 108 | LawnGreen=0x7CFC00, 109 | LemonChiffon=0xFFFACD, 110 | LightBlue=0xADD8E6, 111 | LightCoral=0xF08080, 112 | LightCyan=0xE0FFFF, 113 | LightGoldenrodYellow=0xFAFAD2, 114 | LightGreen=0x90EE90, 115 | LightGrey=0xD3D3D3, 116 | LightPink=0xFFB6C1, 117 | LightSalmon=0xFFA07A, 118 | LightSeaGreen=0x20B2AA, 119 | LightSkyBlue=0x87CEFA, 120 | LightSlateGray=0x778899, 121 | LightSlateGrey=0x778899, 122 | LightSteelBlue=0xB0C4DE, 123 | LightYellow=0xFFFFE0, 124 | Lime=0x00FF00, 125 | LimeGreen=0x32CD32, 126 | Linen=0xFAF0E6, 127 | Magenta=0xFF00FF, 128 | Maroon=0x800000, 129 | MediumAquamarine=0x66CDAA, 130 | MediumBlue=0x0000CD, 131 | MediumOrchid=0xBA55D3, 132 | MediumPurple=0x9370DB, 133 | MediumSeaGreen=0x3CB371, 134 | MediumSlateBlue=0x7B68EE, 135 | MediumSpringGreen=0x00FA9A, 136 | MediumTurquoise=0x48D1CC, 137 | MediumVioletRed=0xC71585, 138 | MidnightBlue=0x191970, 139 | MintCream=0xF5FFFA, 140 | MistyRose=0xFFE4E1, 141 | Moccasin=0xFFE4B5, 142 | NavajoWhite=0xFFDEAD, 143 | Navy=0x000080, 144 | OldLace=0xFDF5E6, 145 | Olive=0x808000, 146 | OliveDrab=0x6B8E23, 147 | Orange=0xFFA500, 148 | OrangeRed=0xFF4500, 149 | Orchid=0xDA70D6, 150 | PaleGoldenrod=0xEEE8AA, 151 | PaleGreen=0x98FB98, 152 | PaleTurquoise=0xAFEEEE, 153 | PaleVioletRed=0xDB7093, 154 | PapayaWhip=0xFFEFD5, 155 | PeachPuff=0xFFDAB9, 156 | Peru=0xCD853F, 157 | Pink=0xFFC0CB, 158 | Plaid=0xCC5533, 159 | Plum=0xDDA0DD, 160 | PowderBlue=0xB0E0E6, 161 | Purple=0x800080, 162 | Red=0xFF0000, 163 | RosyBrown=0xBC8F8F, 164 | RoyalBlue=0x4169E1, 165 | SaddleBrown=0x8B4513, 166 | Salmon=0xFA8072, 167 | SandyBrown=0xF4A460, 168 | SeaGreen=0x2E8B57, 169 | Seashell=0xFFF5EE, 170 | Sienna=0xA0522D, 171 | Silver=0xC0C0C0, 172 | SkyBlue=0x87CEEB, 173 | SlateBlue=0x6A5ACD, 174 | SlateGray=0x708090, 175 | SlateGrey=0x708090, 176 | Snow=0xFFFAFA, 177 | SpringGreen=0x00FF7F, 178 | SteelBlue=0x4682B4, 179 | Tan=0xD2B48C, 180 | Teal=0x008080, 181 | Thistle=0xD8BFD8, 182 | Tomato=0xFF6347, 183 | Turquoise=0x40E0D0, 184 | Violet=0xEE82EE, 185 | Wheat=0xF5DEB3, 186 | White=0xFFFFFF, 187 | WhiteSmoke=0xF5F5F5, 188 | Yellow=0xFFFF00, 189 | YellowGreen=0x9ACD32, 190 | } HTMLColorCode; 191 | }; 192 | struct ANIMATION { 193 | ANIMATION_TYPE type; 194 | uint8_t state; 195 | uint8_t step; 196 | ANIMATION_DIRECTION direction; 197 | CRGB color; 198 | }; 199 | 200 | void 201 | begin(bool swd), 202 | loop(); 203 | 204 | void testLEDs(); 205 | 206 | void 207 | setRedBool(bool power), 208 | setGreenBool(bool power), 209 | setBlueBool(bool power), 210 | setAllBool(bool power), 211 | setAllBool(bool red, bool green, bool blue); 212 | 213 | void 214 | setRed(uint8_t intensity), 215 | setGreen(uint8_t intensity), 216 | setBlue(uint8_t intensity), 217 | setWhite(uint8_t intensity), 218 | setAll(uint8_t red, uint8_t green, uint8_t blue), 219 | setAll(CRGB crgb), 220 | setAll(uint32_t color); 221 | 222 | uint8_t 223 | getRed(), 224 | getGreen(), 225 | getBlue(); 226 | 227 | void defaultIdleAnimation(); 228 | void setIntervalForAnimationType(ANIMATION_TYPE idleType); 229 | unsigned long getIntervalForAnimationType(ANIMATION_TYPE idleType); 230 | void setIdleAnimation(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor); 231 | void setActiveAnimationByDuration(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor, unsigned long duration); 232 | void setActiveAnimationByIteration(ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor, uint8_t iterations); 233 | unsigned long getDurationByIterations(uint8_t iterations, ANIMATION_TYPE animationType); 234 | bool hasActiveAnimation(); 235 | void waitForAnimationToFinish(); 236 | 237 | void disableRedLED(bool disabled); //For SWD 238 | 239 | private: 240 | const uint8_t PIN_RED = 19; 241 | const uint8_t PIN_GREEN = 21; 242 | const uint8_t PIN_BLUE = 17; 243 | 244 | const uint8_t LED_PWM_MIN = 0x01; 245 | const uint8_t LED_PWM_MAX = 0xFE; 246 | 247 | uint8_t _stateRed; 248 | uint8_t _stateGreen; 249 | uint8_t _stateBlue; 250 | 251 | //Animation 252 | BoxTimer _timer; 253 | ANIMATION _idleAnimation; 254 | ANIMATION _activeAnimation; 255 | void setAnimation(ANIMATION* animation, ANIMATION_TYPE animationType, CRGB::HTMLColorCode animationColor); 256 | CRGB _transformPulse(uint8_t state, CRGB originalColor); 257 | CRGB _wheel(uint8_t wheelPos); 258 | void _handleAnimation(ANIMATION* animation); 259 | 260 | bool _redLedDisabled = true; 261 | }; 262 | 263 | #endif 264 | -------------------------------------------------------------------------------- /BoxPlayer.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxPlayer.h" 2 | #include "Hackiebox.h" 3 | 4 | void BoxPlayer::begin() { 5 | Log.info("Init Player..."); 6 | _mode = PLAYER_MODE::NONE; 7 | _flags = PLAYER_FLAGS::NONE; 8 | _dirPath[0] = 0x00; 9 | Log.info("...done"); 10 | } 11 | 12 | void BoxPlayer::loop() { 13 | 14 | } 15 | 16 | void BoxPlayer::play() { 17 | Log.info("Player play"); 18 | Box.boxDAC.play(); 19 | Box.boxPower.feedSleepTimer(); 20 | } 21 | void BoxPlayer::pause() { 22 | Log.info("Player pause"); 23 | Box.boxDAC.pause(); 24 | Box.boxPower.feedSleepTimer(); 25 | } 26 | void BoxPlayer::stop() { 27 | Log.info("Player stop"); 28 | Box.boxDAC.stop(); 29 | Box.boxPower.feedSleepTimer(); 30 | } 31 | void BoxPlayer::rewind() { 32 | //TODO 33 | } 34 | 35 | void BoxPlayer::songEnded() { 36 | Log.info("Song ended with mode=%X and flags=%X...", _mode, _flags); 37 | if (_mode == PLAYER_MODE::FILE) { 38 | if (_flags == PLAYER_FLAGS::LOOP) { 39 | rewind(); 40 | } else { 41 | stop(); 42 | } 43 | } else if (_mode == PLAYER_MODE::DIR) { 44 | _currentSongId++; 45 | if (_currentSongId >= _dirSongCount) { 46 | if (_flags == PLAYER_FLAGS::LOOP) { 47 | _currentSongId = 0; 48 | } else { 49 | Log.info("Played all %i songs from dir %s", _dirSongCount, _dirPath); 50 | stop(); 51 | return; 52 | } 53 | } 54 | 55 | DirFs dir; 56 | if (dir.openDir(_dirPath)) { 57 | char songPath[256]; 58 | uint8_t songIndex = 0; 59 | while (dir.nextFile()) { 60 | if (dir.isDir()) 61 | continue; 62 | if (_currentSongId == songIndex) { 63 | Log.info("Playing next song in dir %s with id %i...", _dirPath, _currentSongId); 64 | sprintf(songPath,"%s/%s", _dirPath, (const char*)dir.fileName()); 65 | } 66 | songIndex++; 67 | } 68 | dir.closeDir(); 69 | if (songIndex > 0) 70 | _play(songPath); 71 | } else { 72 | Log.error("Player could not open dir %s...", _dirPath); 73 | } 74 | } 75 | } 76 | 77 | bool BoxPlayer::_play(const char* path) { 78 | Box.boxPower.feedSleepTimer(); 79 | return Box.boxDAC.playFile(path); 80 | } 81 | 82 | bool BoxPlayer::playDir(const char* path, PLAYER_FLAGS flags) { 83 | Log.info("Playing dir %s", _dirPath); 84 | 85 | char songPath[256]; 86 | 87 | _mode = BoxPlayer::PLAYER_MODE::DIR; 88 | _flags = flags; 89 | _currentSongId = 0; 90 | _dirSongCount = 0; 91 | memcpy((char*)_dirPath, (const char*)path, 256); 92 | 93 | DirFs dir; 94 | if (dir.openDir(_dirPath)) { 95 | while (dir.nextFile()) { 96 | if (dir.isDir()) 97 | continue; 98 | if (_dirSongCount == 0) { 99 | sprintf(songPath,"%s/%s",_dirPath, (const char*)dir.fileName()); 100 | } 101 | _dirSongCount++; 102 | } 103 | dir.closeDir(); 104 | if (_dirSongCount > 0) { 105 | return _play(songPath); 106 | } else { 107 | Log.error("Player could not find songs to play..."); 108 | } 109 | } else { 110 | Log.error("Player could not open dir %s...", _dirPath); 111 | } 112 | return false; 113 | } 114 | bool BoxPlayer::playFile(const char* file, PLAYER_FLAGS flags) { 115 | _mode = PLAYER_MODE::FILE; 116 | _flags = flags; 117 | Log.info("Playing file %s", file); 118 | return _play(file); 119 | } 120 | 121 | bool BoxPlayer::isPlaying() { 122 | return (Box.boxDAC.audioPlaying); 123 | } 124 | bool BoxPlayer::isStopped() { 125 | return (Box.boxDAC.hasStopped()); 126 | } -------------------------------------------------------------------------------- /BoxPlayer.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxPlayer_h 2 | #define BoxPlayer_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | class BoxPlayer { 7 | public: 8 | enum class PLAYER_EVENT { 9 | }; 10 | enum class PLAYER_MODE { 11 | NONE = 0x0, 12 | FILE = 0x1, 13 | DIR = 0x2 14 | }; 15 | 16 | enum class PLAYER_FLAGS { 17 | NONE = 0x0, 18 | LOOP = 0x1, 19 | RANDOM = 0x2 20 | }; 21 | void 22 | begin(), 23 | loop(); 24 | 25 | void 26 | play(), 27 | pause(), 28 | stop(), 29 | next(), 30 | previous(), 31 | rewind(), 32 | songEnded(); 33 | 34 | bool 35 | playDir(const char* path, PLAYER_FLAGS flags), 36 | playFile(const char* file, PLAYER_FLAGS flags); 37 | 38 | bool 39 | isPlaying(), 40 | isStopped(); 41 | 42 | private: 43 | PLAYER_MODE _mode; 44 | PLAYER_FLAGS _flags; 45 | char _dirPath[256]; 46 | uint8_t _currentSongId; 47 | uint8_t _dirSongCount; 48 | 49 | bool _play(const char* path); 50 | 51 | }; 52 | 53 | #endif -------------------------------------------------------------------------------- /BoxPower.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxPower.h" 2 | 3 | #include "Hackiebox.h" 4 | #include 5 | 6 | void BoxPower::initPins() { 7 | pinMode(58, OUTPUT); //SD Power pin 8 | pinMode(61, OUTPUT); //Audio, Accelerometer, RFID, LED Blue / Red? 9 | } 10 | 11 | void BoxPower::begin() { 12 | _sleepMinutes = Config.get()->battery.sleepMinutes; 13 | _lastFeed = millis(); 14 | setInterval(5000); 15 | 16 | Log.info("Init BoxPower, sleepMinutes=%i", _sleepMinutes); 17 | } 18 | 19 | void BoxPower::loop() { 20 | uint32_t millis_tmp = millis(); 21 | uint32_t deltaMinutes = (millis_tmp - _lastFeed) / (1000 * 60); 22 | if (_sleepMinutes > 0 && deltaMinutes >= _sleepMinutes) { 23 | Log.verbose("millis_tmp=%l, _lastFeed=%l, deltaMinutes=%l", millis_tmp, _lastFeed, deltaMinutes); 24 | Events.handlePowerEvent(PowerEvent::BOX_IDLE); 25 | } 26 | } 27 | 28 | void BoxPower::feedSleepTimer() { 29 | feedSleepTimerSilent(); 30 | Log.verbose("Sleep timer rst, _lastFeed=%l, Mem: free(str/ptr/cny/end) Stack: %ib(%X/%X/%X/%X), Heap: %ib(%X/%X/%X/%X)", 31 | _lastFeed, 32 | freeStackMemory(), (uint32_t)stackStart()-0x20004000, (uint32_t)stackPointer()-0x20004000, (uint32_t)getFirstStackCanary()-0x20004000, (uint32_t)stackEnd()-0x20004000, 33 | freeHeapMemory(), (uint32_t)heapStart()-0x20004000, (uint32_t)heapPointer()-0x20004000, (uint32_t)getFirstHeapCanary()-0x20004000, (uint32_t)heapEnd()-0x20004000 34 | ); 35 | uint32_t stackCanaries = countStackCanaries(); 36 | uint32_t heapCanaries = countHeapCanaries(); 37 | if (stackCanaries < 10 || heapCanaries < 10) { 38 | Log.error("!!! Canaries low !!! Stack=%l, Heap=%l", stackCanaries, heapCanaries); 39 | } 40 | } 41 | void BoxPower::feedSleepTimerSilent() { 42 | _lastFeed = millis(); 43 | Box.watchdog_feed(); 44 | } 45 | 46 | void BoxPower::_preparePowerDown() { 47 | Log.info("Prepare power down..."); 48 | //TODO 49 | //smartconfig down 50 | Box.logStreamMulti.flush(); 51 | Box.boxPlayer.stop(); 52 | setSdPower(false); 53 | setOtherPower(false); 54 | Box.boxLEDs.setAllBool(false); 55 | Serial.end(); 56 | Box.watchdog_stop(); 57 | } 58 | void BoxPower::reset() { 59 | Events.handlePowerEvent(PowerEvent::PRE_RESET); 60 | 61 | _preparePowerDown(); 62 | PRCMMCUReset(true); 63 | } 64 | void BoxPower::hibernate() { 65 | Events.handlePowerEvent(PowerEvent::PRE_HIBERNATE); 66 | 67 | _preparePowerDown(); 68 | //enable ear wakeup interrupt 69 | PRCMHibernateWakeupSourceEnable(PRCM_HIB_GPIO2 | PRCM_HIB_GPIO4); 70 | //TODO 71 | //Utils_SpiFlashDeepPowerDown(); 72 | PRCMHibernateEnter(); 73 | } 74 | 75 | void BoxPower::setSdPower(bool power) { 76 | digitalWrite(58, !power); 77 | } 78 | void BoxPower::setOtherPower(bool power) { 79 | digitalWrite(61, power); 80 | 81 | if (power) { 82 | //RESET Chips 83 | pinMode(62, OUTPUT); 84 | digitalWrite(62, LOW); 85 | Box.delayTask(1); 86 | digitalWrite(62, HIGH); 87 | Box.delayTask(10); 88 | } 89 | } 90 | 91 | bool BoxPower::getSdPower() { 92 | return !digitalRead(58); 93 | } 94 | bool BoxPower::getOtherPower() { 95 | return digitalRead(61); 96 | } -------------------------------------------------------------------------------- /BoxPower.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxPower_h 2 | #define BoxPower_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | #include 7 | 8 | class BoxPower : public EnhancedThread { 9 | public: 10 | enum class PowerEvent { 11 | PRE_HIBERNATE, 12 | PRE_RESET, 13 | BOX_IDLE, 14 | }; 15 | void 16 | initPins(), 17 | begin(), 18 | loop(); 19 | 20 | void 21 | feedSleepTimer(), 22 | feedSleepTimerSilent(), 23 | reset(), 24 | hibernate(); 25 | 26 | void 27 | setSdPower(bool power), 28 | setOtherPower(bool power); 29 | 30 | bool 31 | getSdPower(), 32 | getOtherPower(); 33 | 34 | private: 35 | uint8_t _sleepMinutes; 36 | uint32_t _lastFeed; 37 | 38 | void _preparePowerDown(); 39 | }; 40 | 41 | #endif -------------------------------------------------------------------------------- /BoxRFID.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxRFID_h 2 | #define BoxRFID_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | #include 7 | 8 | class BoxRFID : public EnhancedThread { 9 | public: 10 | enum class TAG_EVENT { 11 | TAG_PLACED, 12 | TAG_REMOVED 13 | //HAND_PLACED, //TODO IRQ_STATUS=0x02 - Collsion 14 | }; 15 | 16 | void 17 | begin(), 18 | loop(); 19 | 20 | uint8_t 21 | readRegister(uint8_t regi); 22 | void 23 | writeRegister(uint8_t regi, uint8_t value), 24 | sendCommand(uint8_t command); 25 | 26 | void receivedInterrupt(); 27 | 28 | void logUID(); 29 | void getUID(uint8_t* uid); 30 | uint8_t tagUid[8]; 31 | bool tagActive; 32 | 33 | uint8_t readBlocks(uint8_t* data, uint8_t maxBlocks); 34 | void logTagMemory(); 35 | bool dumpTagMemory(bool overwrite); 36 | 37 | private: 38 | void turnFieldOn(); 39 | void turnFieldOff(); 40 | 41 | enum class REG_CMD_WORD_BITS : uint8_t { 42 | COMMAND_B7 = 0b10000000, 43 | REGISTER_B7 = 0b00000000, 44 | 45 | READ_B6 = 0b01000000, 46 | WRITE_B6 = 0b00000000, 47 | 48 | CONTINUOUS_MODE_REG_B5 = 0b00100000 49 | }; 50 | enum class DIRECT_COMMANDS : uint8_t{ 51 | IDLING = 0x00, 52 | SOFT_INIT = 0x03, 53 | RESET_FIFO = 0x0F, 54 | TRANSMIT_NO_CRC = 0x10, 55 | TRANSMIT_CRC = 0x11, 56 | TRANSMIT_DELAY_NO_CRC = 0x12, 57 | TRANSMIT_DELAY_CRC = 0x13, 58 | TRANSMT_NEXT_SLOT = 0x14, 59 | BLOCK_RECIEVER = 0x16, 60 | ENABLE_RECIEVER = 0x17, 61 | TEST_INTERNAL_RF = 0x18, 62 | TEST_EXTERNAL_RF = 0x19, 63 | RECEIVER_GAIN_ADJ = 0x1A 64 | }; 65 | enum class REGISTER : uint8_t { 66 | CHIP_STATUS_CONTROL = 0x00, 67 | ISO_CONTROL = 0x01, 68 | ISO14443B_TX_OPTIONS = 0x02, 69 | ISO14443A_BITRATE_OPTIONS = 0x03, 70 | TX_TIMER_EPC_HIGH = 0x04, 71 | TX_TIMER_EPC_LOW = 0x05, 72 | TX_PULSE_LENGTH_CONTROL = 0x06, 73 | RX_NO_RESPONSE_WAIT_TIME = 0x07, 74 | RX_WAIT_TIME = 0x08, 75 | MODULATOR_CONTROL = 0x09, 76 | RX_SPECIAL_SETTINGS = 0x0A, 77 | REGULATOR_CONTROL = 0x0B, 78 | IRQ_STATUS = 0x0C, 79 | IRQ_MASK = 0x0D, 80 | COLLISION_POSITION = 0x0E, 81 | RSSI_LEVELS = 0x0F, 82 | SPECIAL_FUNCTION_1 = 0x10, 83 | TEST_SETTINGS_1 = 0x1A, 84 | TEST_SETTINGS_2 = 0x1B, 85 | FIFO_STATUS = 0x1C, 86 | TX_LENGTH_BYTE_1 = 0x1D, 87 | TX_LENGTH_BYTE_2 = 0x1E, 88 | FIFO = 0x1F 89 | }; 90 | enum class IRQ_STATUS : uint8_t { 91 | IDLING = 0x00, 92 | NO_RESPONSE = 0x01, 93 | COLLISION_ERROR = 0x02, 94 | FRAMING_ERROR = 0x04, 95 | PARITY_ERROR = 0x08, 96 | CRC_ERROR = 0x10, 97 | FIFO_HIGH_OR_LOW = 0x20, 98 | RX_COMPLETE = 0x40, 99 | TX_COMPLETE = 0x80 100 | }; 101 | 102 | enum class TRF_STATUS { 103 | TRF_IDLE = 0x00, 104 | TX_COMPLETE = 0x01, 105 | RX_COMPLETE = 0x02, 106 | TX_ERROR = 0x03, 107 | RX_WAIT = 0x04, 108 | RX_WAIT_EXTENSION = 0x05, 109 | TX_WAIT = 0x06, 110 | PROTOCOL_ERROR = 0x07, 111 | COLLISION_ERROR = 0x08, 112 | NO_RESPONSE_RECEIVED = 0x09, 113 | NO_RESPONSE_RECEIVED_15693 = 0x0A 114 | }; 115 | 116 | enum class ISO15693_RESULT { 117 | NO_RESPONSE = 0x00, 118 | VALID_RESPONSE = 0x01, 119 | INVALID_RESPONSE = 0x02, 120 | 121 | INVENTORY_NO_RESPONSE = 0x10, 122 | INVENTORY_VALID_RESPONSE = 0x11, 123 | INVENTORY_INVALID_RESPONSE = 0x12, 124 | 125 | GET_RANDOM_NO_RESPONSE = 0x20, 126 | GET_RANDOM_VALID = 0x21, 127 | GET_RANDOM_INVALID = 0x22, 128 | 129 | SET_PASSWORD_NO_RESPONSE = 0x30, 130 | SET_PASSWORD_CORRECT = 0x31, 131 | SET_PASSWORD_INCORRECT = 0x32, 132 | 133 | READ_SINGLE_BLOCK_NO_RESPONSE = 0x40, 134 | READ_SINGLE_BLOCK_VALID_RESPONSE = 0x41, 135 | READ_SINGLE_BLOCK_INVALID_RESPONSE = 0x42, 136 | }; 137 | 138 | void 139 | setSlaveSelect(bool enabled), 140 | spiEnable(), 141 | spiDisable(); 142 | 143 | /* 144 | void startReadRegister(uint8_t regi); 145 | void startWriteRegister(uint8_t regi); 146 | void endReadWriteRegister(); 147 | */ 148 | 149 | const uint8_t IRQ_PIN = 18; 150 | const static uint8_t FIFO_SIZE = 100; 151 | 152 | uint8_t 153 | readRegister(REGISTER regi); 154 | void 155 | sendCommand(DIRECT_COMMANDS command), 156 | writeRegister(REGISTER regi, uint8_t value); 157 | 158 | void sendRaw(uint8_t* buffer, uint8_t length); 159 | void sendRawSPI(uint8_t* buffer, uint8_t length, bool continuedSend); 160 | 161 | void 162 | readRegisterCont(uint8_t* buffer, uint8_t length), 163 | readRegisterCont(uint8_t regi, uint8_t* buffer, uint8_t length), 164 | readRegisterCont(REGISTER regi, uint8_t* buffer, uint8_t length); 165 | 166 | uint8_t readIrqRegister(); 167 | void clearIrqRegister(); 168 | 169 | void processInterrupt(IRQ_STATUS irqStatus); 170 | bool readInterrupt(); 171 | void clearInterrupt(); 172 | bool interrupt; 173 | 174 | TRF_STATUS waitRxData(uint8_t txTimeout, uint8_t rxTimeout); 175 | void waitTxIRQ(uint8_t txTimeout); 176 | void waitRxIRQ(uint8_t rxTimeout); 177 | void timeoutIRQ(); 178 | 179 | ISO15693_RESULT ISO15693_sendSingleSlotInventory(uint8_t* uid); 180 | ISO15693_RESULT ISO15693_getRandomSlixL(uint8_t* random); 181 | ISO15693_RESULT ISO15693_setPassSlixL(uint8_t pass_id, uint32_t password); 182 | ISO15693_RESULT ISO15693_readSingleBlock(uint8_t blockId, uint8_t* blockData); 183 | 184 | void reinitRFID(); 185 | 186 | TRF_STATUS trfStatus; 187 | uint8_t trfBuffer[FIFO_SIZE]; //may reduce size 188 | uint8_t trfOffset; 189 | uint8_t trfRxLength; 190 | 191 | void resetRFID(); 192 | void initRFID(); 193 | 194 | TRF_STATUS sendDataTag(uint8_t *sendBuffer, uint8_t sendLen); 195 | TRF_STATUS sendDataTag(uint8_t *sendBuffer, uint8_t sendLen, uint8_t txTimeout, uint8_t rxTimeout); 196 | }; 197 | 198 | #endif -------------------------------------------------------------------------------- /BoxSD.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxSD.h" 2 | 3 | void BoxSD::begin() { 4 | _initialized = false; 5 | Log.info("Init SD card"); 6 | int result = FatFs.begin(); 7 | if (!result) { 8 | Log.error("SD not mounted. Code %i", FatFs.error()); 9 | return; 10 | } 11 | 12 | Log.info(" Capacity: %iMB", FatFs.capacity()/1024); 13 | Log.info(" Free: %iMB", FatFs.free()/1024); 14 | _initialized = true; 15 | } 16 | 17 | void BoxSD::loop() { 18 | 19 | } 20 | 21 | bool BoxSD::isInitialized() { 22 | return _initialized; 23 | } 24 | 25 | void BoxSD::webJsonListDir(WebServer* webServer, char* directory) { 26 | DirFs dir; 27 | if (dir.openDir(directory)) { 28 | webServer->sendContent("{\"files\":["); 29 | bool firstRound = true; 30 | while (dir.nextFile()) { 31 | StaticJsonDocument<361> file; //Maximum 256 chars filename length //https://arduinojson.org/v6/assistant/ 32 | 33 | file["name"] = dir.fileName(); 34 | file["size"] = dir.fileSize(); 35 | file["time"] = dir.fileModTime(); 36 | file["date"] = dir.fileModDate(); 37 | file["dir"] = dir.isDir(); 38 | 39 | size_t len = measureJson(file)+1; 40 | char json[len]; 41 | serializeJson(file, json, len); //TODO directly stream to save mem 42 | if (!firstRound) 43 | webServer->sendContent(","); 44 | webServer->sendContent(json); 45 | firstRound = false; 46 | } 47 | dir.closeDir(); 48 | webServer->sendContent("]}"); 49 | } else { 50 | StaticJsonDocument<299> doc; //Maximum 256 chars path length //https://arduinojson.org/v6/assistant/ 51 | doc["error"] = "Dir not found"; 52 | Log.error("Dir %s not found", directory); 53 | 54 | size_t len = measureJson(doc)+1; 55 | char json[len]; 56 | serializeJson(doc, json, len); //TODO directly stream to save mem 57 | webServer->sendContent(json); 58 | } 59 | } -------------------------------------------------------------------------------- /BoxSD.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxSD_h 2 | #define BoxSD_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | #include 7 | 8 | class BoxSD { 9 | public: 10 | void 11 | begin(), 12 | loop(); 13 | 14 | bool isInitialized(); 15 | 16 | void webJsonListDir(WebServer* webServer, char* directory); 17 | private: 18 | bool _initialized = false; 19 | }; 20 | 21 | #endif -------------------------------------------------------------------------------- /BoxTimer.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxTimer.h" 2 | 3 | void BoxTimer::setTimer(unsigned long milliseconds) { 4 | _currentMillis = millis(); 5 | _endMillis = _currentMillis + milliseconds; 6 | _isRunning = true; 7 | _hasChanged = true; 8 | } 9 | 10 | void BoxTimer::tick() { 11 | if (!_isRunning) { 12 | _hasChanged = false; 13 | } else { 14 | _currentMillis = millis(); 15 | if (_endMillis <= _currentMillis) { 16 | _isRunning = false; 17 | _hasChanged = true; 18 | } 19 | } 20 | } 21 | 22 | unsigned long BoxTimer::getTimeTillEnd() { 23 | if (!_isRunning) 24 | return 0; 25 | return _endMillis - _currentMillis; 26 | } 27 | 28 | bool BoxTimer::isRunning() { 29 | return _isRunning; 30 | } 31 | 32 | bool BoxTimer::wasRunning() { 33 | return !_isRunning && _hasChanged; 34 | } 35 | 36 | void BoxTimer::stopTimer() { 37 | _isRunning = false; 38 | } 39 | -------------------------------------------------------------------------------- /BoxTimer.h: -------------------------------------------------------------------------------- 1 | #ifndef BoxTimer_h 2 | #define BoxTimer_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | class BoxTimer { 7 | public: 8 | void setTimer(unsigned long milliseconds); 9 | void stopTimer(); 10 | void tick(); 11 | bool isRunning(); 12 | bool wasRunning(); 13 | unsigned long getTimeTillEnd(); 14 | private: 15 | bool _isRunning; 16 | bool _hasChanged; 17 | unsigned long _currentMillis; 18 | unsigned long _endMillis; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /BoxTonies.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxTonies.h" 2 | 3 | bool BoxTonies::loadTonieByUid(uint8_t uid[8]) { 4 | uint8_t path[strlen(CONTENT_BASE)+16+1+1]; 5 | 6 | sprintf( 7 | (char*)path, 8 | "%s%02X%02X%02X%02X/%02X%02X%02X%02X", 9 | CONTENT_BASE, 10 | uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7] 11 | ); 12 | 13 | memcpy(currentUid, uid, 8); 14 | if (loadTonieByPath(path)) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | 20 | bool BoxTonies::loadTonieByPath(uint8_t* path) { 21 | bool ret = true; 22 | clearHeader(); 23 | 24 | Log.info("Load Tonie from path %s...", path); 25 | 26 | if (tonieFile.open((char*)path, FA_OPEN_EXISTING | FA_READ)) { 27 | uint8_t buffer[512]; //TODO: buffer >512 size may scramble the stream 4096 block needed 28 | uint32_t read = tonieFile.read(buffer, sizeof(buffer)); 29 | uint16_t bufferLen = read; 30 | if (read > 0) { 31 | uint16_t cursor = 0; 32 | uint8_t readBytes; 33 | 34 | uint8_t magicBytes[] = { 0x00, 0x00, 0x0F, 0xFC };//==4092 which is the length of the protobuf block 35 | 36 | if (memcmp(magicBytes, buffer, 4) == 0) { 37 | cursor += 4; 38 | while (cursor < bufferLen-1) { 39 | uint8_t fieldId = buffer[cursor]>>3; 40 | uint8_t fieldType = buffer[cursor]&0b00000111; 41 | cursor++; 42 | 43 | if (fieldId == 1 && fieldType == 2) { //Audio data SHA-1 hash 44 | uint64_t size = readVariant(&buffer[cursor], bufferLen-cursor, readBytes); 45 | cursor += readBytes; 46 | if (size == 20) { 47 | memcpy(&header.hash[0], &buffer[cursor], size); 48 | cursor += size; 49 | } else { 50 | Log.error("... hash length should be 20 but is %i", size); 51 | ret = false; 52 | break; 53 | } 54 | } else if (fieldId == 2 && fieldType == 0) { //Audio data length in bytes 55 | header.audioLength = (uint32_t)readVariant(&buffer[cursor], bufferLen-cursor, readBytes); 56 | cursor += readBytes; 57 | } else if (fieldId == 3 && fieldType == 0) { //Audio-ID of OGG audio file, which is the unix time stamp of file creation 58 | header.audioId = (uint32_t)readVariant(&buffer[cursor], bufferLen-cursor, readBytes); 59 | cursor += readBytes; 60 | } else if (fieldId == 4 && fieldType == 2) { //[array of variant] Ogg page numbers for Chapters 61 | uint8_t blockEnd = (uint8_t)readVariant(&buffer[cursor], bufferLen-cursor, readBytes); 62 | cursor += readBytes; 63 | blockEnd += cursor; 64 | uint16_t blockStart = cursor; 65 | 66 | header.audioChapterCount = 0; 67 | while(cursor 79 | uint64_t fillByteCount = readVariant(&buffer[cursor], bufferLen-cursor, readBytes); 80 | cursor += readBytes; 81 | 82 | if (fillByteCount + cursor != 4096) { 83 | Log.error("... Header length should be 4096 but is %i", fillByteCount + cursor); 84 | ret = false; 85 | } 86 | 87 | break; //everything read. 88 | } else { 89 | Log.error("... found unexpected protobuf field with id=%i and type=%i", fieldId, fieldType); 90 | ret = false; 91 | //clear header 92 | break; 93 | } 94 | } 95 | logTonieHeader(); 96 | } else { 97 | Log.error("... unexpected beginning of file %X %X %X %X", buffer[0], buffer[1], buffer[2], buffer[3]); 98 | ret = false; 99 | } 100 | } else { 101 | Log.error("... couldn't data from file"); 102 | ret = false; 103 | } 104 | tonieFile.close(); 105 | } else { 106 | Log.error("... couldn't open Tonie"); 107 | ret = false; 108 | } 109 | if (!ret) { 110 | clearHeader(); 111 | } 112 | return ret; 113 | } 114 | 115 | void BoxTonies::clearHeader() { 116 | //header.hash = {0}; //TODO 117 | header.audioLength = 0; 118 | header.audioId = 0; 119 | header.audioChapterCount = 0; 120 | for (uint8_t i=0; i<99; i++) { 121 | header.audioChapters[i] = 0; 122 | } 123 | 124 | } 125 | 126 | uint64_t BoxTonies::readVariant(uint8_t* buffer, uint16_t length, uint8_t& readBytes) { 127 | uint64_t ret = 0; 128 | readBytes = 0; 129 | while (readBytesEvent); 11 | } 12 | int Report(const char *format, ...) { 13 | //Workaround for defined Report in the WiFi/utility/ 14 | va_list args; 15 | va_start(args, format); 16 | Log.printFormat(format, args); 17 | return 0; 18 | } 19 | void crash(crashSource source, uint32_t* sp) { 20 | //Box.logStreamSse.setSsePaused(true); 21 | Log.info("crashSource=%i, sp=%X, sp=%X", source, sp, (uint32_t)sp-0x20004000); 22 | 23 | if (source == CRASH_TEST) 24 | return; 25 | 26 | Box.boxPower.feedSleepTimer(); 27 | 28 | FileFs _file; 29 | bool _isOpen; 30 | Log.info("Dumping SRAM 0x20004000-0x20040000 to /revvox/memdump..."); 31 | _isOpen = _file.open("/revvox/memdump", FA_CREATE_ALWAYS | FA_WRITE); 32 | if (_isOpen) { 33 | _file.write((void *)0x20004000, 0x3C000); 34 | _file.close(); 35 | } 36 | Log.info("...done"); 37 | Log.info("Dumping REGISTERS 0xE000E000-0xE000F000 to /revvox/regdump..."); 38 | _isOpen = _file.open("/revvox/regdump", FA_CREATE_ALWAYS | FA_WRITE); 39 | if (_isOpen) { 40 | _file.write((void *)0xE000E000, 0x1000); 41 | _file.close(); 42 | } 43 | Log.info("...done"); 44 | 45 | Box.logStreamMulti.flush(); //Write all buffered logs to SD/SSE 46 | 47 | __asm__ volatile("bkpt"); 48 | 49 | Box.boxPower.hibernate(); 50 | } 51 | void Hackiebox::setup() { 52 | if (!watchdog_start(10)) { 53 | watchdog_stop(); 54 | //reset box?! 55 | } 56 | uint32_t stackCanaries = countStackCanaries(); 57 | uint32_t heapCanaries = countHeapCanaries(); 58 | setCanaries(); 59 | 60 | inDelayTask = true; 61 | logStreamMulti.setSlot(&logStreamSd, 0); 62 | logStreamMulti.setSlot(&logStreamSse, 1); 63 | Log.init(LOG_LEVEL_VERBOSE, 921600, &logStreamMulti); 64 | Log.info("Booting Hackiebox..."); 65 | Box.boxPower.feedSleepTimer(); 66 | Log.info(" -sizes: stack=%X, heap=%X", stackStart()-stackEnd(), heapEnd()-heapStart()); 67 | Log.info(" -prev. canaries: stack=%ix, heap=%ix", stackCanaries, heapCanaries); 68 | 69 | register_crash_callback(crash); 70 | crashed(CRASH_TEST, 0); 71 | 72 | Wire.begin(); 73 | 74 | boxPower.initPins(); 75 | boxPower.setSdPower(true); 76 | boxPower.setOtherPower(true); 77 | 78 | boxSD.begin(); 79 | Config.begin(); //SD Card needed! 80 | ConfigStruct* config = Config.get(); 81 | 82 | watchdog_start(config->misc.watchdogSeconds); 83 | 84 | boxPower.begin(); 85 | boxI2C.begin(); 86 | boxLEDs.begin(config->misc.swd); 87 | boxLEDs.setAll(BoxLEDs::CRGB::White); 88 | boxBattery.begin(); 89 | boxLEDs.setAll(BoxLEDs::CRGB::Orange); 90 | boxEars.begin(); 91 | boxLEDs.setAll(BoxLEDs::CRGB::Yellow); 92 | boxAccel.begin(); 93 | boxLEDs.setAll(BoxLEDs::CRGB::Pink); 94 | boxRFID.begin(); 95 | boxLEDs.setAll(BoxLEDs::CRGB::Teal); 96 | //boxDAC.begin(); 97 | boxLEDs.setAll(BoxLEDs::CRGB::Fuchsia); 98 | 99 | boxCLI.begin(); 100 | 101 | Box.boxPower.feedSleepTimerSilent(); 102 | boxWiFi = WrapperWiFi(config->wifi.ssid, config->wifi.password); 103 | boxWiFi.begin(); 104 | Box.boxPower.feedSleepTimerSilent(); 105 | 106 | webServer = WrapperWebServer(); 107 | webServer.begin(); 108 | 109 | boxPlayer = BoxPlayer(); 110 | boxPlayer.begin(); 111 | 112 | boxAccel.setName("Accelerometer"); 113 | boxBattery.setName("Battery"); 114 | boxBattery.batteryTestThread.setName("Battery.Test"); 115 | boxCLI.setName("CLI"); 116 | boxDAC.setName("DAC"); 117 | boxRFID.setName("RFID"); 118 | boxEars.setName("Ears"); 119 | boxLEDs.setName("LEDs"); 120 | boxPower.setName("Power"); 121 | boxWiFi.setName("WiFi"); 122 | webServer.setName("Webserver"); 123 | 124 | boxDAC.priority = 0; 125 | boxRFID.priority = 1; 126 | boxAccel.priority = 2; 127 | boxLEDs.priority = 3; 128 | boxEars.priority = 4; 129 | webServer.priority = 5; 130 | boxPower.priority = 10; 131 | boxWiFi.priority = 50; 132 | boxBattery.priority = 100; 133 | boxBattery.batteryTestThread.priority = 100; 134 | boxCLI.priority = 100; 135 | 136 | threadController = ThreadController(); 137 | threadController.add(&boxAccel); 138 | threadController.add(&boxBattery); 139 | threadController.add(&boxBattery.batteryTestThread); 140 | threadController.add(&boxCLI); 141 | threadController.add(&boxDAC); 142 | threadController.add(&boxEars); 143 | threadController.add(&boxRFID); 144 | threadController.add(&boxLEDs); 145 | threadController.add(&boxPower); 146 | threadController.add(&boxWiFi); 147 | threadController.add(&webServer); 148 | threadController.sortThreads(); 149 | 150 | Log.info("Config: %s", Config.getAsJson().c_str()); 151 | 152 | boxAccel.onRun(ThreadCallbackHandler([&]() { boxAccel.loop(); })); 153 | boxBattery.onRun(ThreadCallbackHandler([&]() { boxBattery.loop(); })); 154 | boxBattery.batteryTestThread.onRun(ThreadCallbackHandler([&]() { boxBattery.doBatteryTestStep(); })); 155 | boxCLI.onRun(ThreadCallbackHandler([&]() { boxCLI.loop(); })); 156 | boxDAC.onRun(ThreadCallbackHandler([&]() { boxDAC.loop(); })); 157 | boxEars.onRun(ThreadCallbackHandler([&]() { boxEars.loop(); })); 158 | boxRFID.onRun(ThreadCallbackHandler([&]() { boxRFID.loop(); })); 159 | boxLEDs.onRun(ThreadCallbackHandler([&]() { boxLEDs.loop(); })); 160 | boxPower.onRun(ThreadCallbackHandler([&]() { boxPower.loop(); })); 161 | boxWiFi.onRun(ThreadCallbackHandler([&]() { boxWiFi.loop(); })); 162 | webServer.onRun(ThreadCallbackHandler([&]() { webServer.loop(); })); 163 | 164 | //logStreamSse.setSsePaused(false); 165 | 166 | boxLEDs.defaultIdleAnimation(); 167 | Log.info("Hackiebox started!"); 168 | Box.boxPower.feedSleepTimer(); 169 | inDelayTask = false; 170 | 171 | boxDAC.begin(); 172 | //Workaround, as something seems to interfere / remove the irq. 173 | //But box now crashes! 174 | boxDAC.i2sStartMicros = micros(); 175 | } 176 | 177 | void Hackiebox::delayTask(uint16_t millis) { 178 | if (!inDelayTask) { 179 | inDelayTask = true; 180 | BoxTimer timer; 181 | timer.setTimer(millis); 182 | 183 | //Work start 184 | while (timer.isRunning()) { 185 | delayTaskWork(timer.getTimeTillEnd()); 186 | timer.tick(); 187 | } 188 | //Work end 189 | 190 | inDelayTask = false; 191 | } else { 192 | delay(millis); 193 | } 194 | } 195 | void Hackiebox::delayTaskWork(uint16_t millis) { 196 | //delay(millis); 197 | boxDAC.loop(millis); 198 | //if (millis > 100) 199 | // Log.debug("Delay %i", millis); 200 | //boxDAC.generateZeroAudio(millis); 201 | } 202 | 203 | void Hackiebox::loop() { 204 | watchdog_feed(); 205 | threadController.run(); 206 | } 207 | 208 | bool Hackiebox::watchdog_isFed() { 209 | return _watchdog_fed; 210 | } 211 | void Hackiebox::watchdog_feed() { 212 | _watchdog_fed = true; 213 | } 214 | void Hackiebox::watchdog_unfeed() { 215 | _watchdog_fed = false; 216 | } 217 | void watchdog_handler() { 218 | if (Box.watchdog_isFed() || !Box.watchdog_enabled) { 219 | MAP_WatchdogIntClear(WDT_BASE); 220 | Box.watchdog_unfeed(); 221 | } 222 | } 223 | bool Hackiebox::watchdog_start(uint8_t timeoutS) { 224 | watchdog_feed(); 225 | if (timeoutS == 0) { 226 | watchdog_start(53); 227 | //watchdog_stop(); // Random watchdog triggers?! 228 | watchdog_enabled = false; 229 | } else { 230 | if (timeoutS > 53) 231 | timeoutS = 53; //otherwise uint32_t of WatchdogReloadSet will overflow. 232 | 233 | MAP_PRCMPeripheralClkEnable(PRCM_WDT, PRCM_RUN_MODE_CLK); 234 | MAP_WatchdogUnlock(WDT_BASE); 235 | MAP_IntPrioritySet(INT_WDT, INT_PRIORITY_LVL_1); 236 | MAP_WatchdogStallEnable(WDT_BASE); //Allow Debugging 237 | MAP_WatchdogIntRegister(WDT_BASE, watchdog_handler); 238 | MAP_WatchdogReloadSet(WDT_BASE, 80000000*timeoutS); 239 | MAP_WatchdogEnable(WDT_BASE); 240 | 241 | watchdog_enabled = true; 242 | } 243 | return MAP_WatchdogRunning(WDT_BASE); 244 | } 245 | void Hackiebox::watchdog_stop() { 246 | MAP_WatchdogUnlock(WDT_BASE); 247 | MAP_WatchdogReloadSet(WDT_BASE, 0xFFFFFFFF); //set timer to high value 248 | MAP_WatchdogIntClear(WDT_BASE); 249 | MAP_WatchdogIntUnregister(WDT_BASE); 250 | 251 | watchdog_enabled = false; 252 | } 253 | 254 | -------------------------------------------------------------------------------- /Hackiebox.h: -------------------------------------------------------------------------------- 1 | #ifndef Hackiebox_h 2 | #define Hackiebox_h 3 | 4 | #include "BaseHeader.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "BoxAccelerometer.h" 14 | #include "BoxBattery.h" 15 | #include "BoxButtonEars.h" 16 | #include "BoxCLI.h" 17 | #include "BoxDAC.h" 18 | #include "BoxEvents.h" 19 | #include "BoxI2C.h" 20 | #include "BoxLEDs.h" 21 | #include "BoxPlayer.h" 22 | #include "BoxPower.h" 23 | #include "BoxRFID.h" 24 | #include "BoxSD.h" 25 | #include "BoxTonies.h" 26 | 27 | #include "WrapperWiFi.h" 28 | #include "WrapperWebServer.h" 29 | 30 | #include "LogStreamMulti.h" 31 | #include "LogStreamSd.h" 32 | #include "LogStreamSse.h" 33 | 34 | class Hackiebox { 35 | public: 36 | 37 | void 38 | setup(), 39 | loop(); 40 | 41 | bool 42 | watchdog_start(uint8_t timeoutS), 43 | watchdog_isFed(); 44 | void 45 | watchdog_stop(), 46 | watchdog_feed(), 47 | watchdog_unfeed(); 48 | 49 | bool inDelayTask; 50 | void delayTask(uint16_t millis); 51 | void delayTaskWork(uint16_t millis); 52 | 53 | 54 | ThreadController threadController; 55 | 56 | BoxAccelerometer boxAccel; 57 | BoxBattery boxBattery; 58 | BoxButtonEars boxEars; 59 | BoxCLI boxCLI; 60 | BoxDAC boxDAC; 61 | BoxI2C boxI2C; 62 | BoxLEDs boxLEDs; 63 | BoxPower boxPower; 64 | BoxPlayer boxPlayer; 65 | BoxRFID boxRFID; 66 | BoxSD boxSD; 67 | BoxTonies boxTonie; 68 | WrapperWiFi boxWiFi; 69 | WrapperWebServer webServer; 70 | 71 | LogStreamMulti logStreamMulti; 72 | LogStreamSd logStreamSd; 73 | LogStreamSse logStreamSse; 74 | 75 | bool watchdog_enabled; 76 | private:/* 77 | typedef void (*fAPPWDTDevCallbk)(); 78 | void 79 | watchdog_handler();*/ 80 | 81 | bool _watchdog_fed; 82 | }; 83 | extern Hackiebox Box; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /LogStreamMulti.cpp: -------------------------------------------------------------------------------- 1 | #include "LogStreamMulti.h" 2 | 3 | //LogStreamMulti::LogStreamMulti(): Stream() {} 4 | void LogStreamMulti::setSlot(Stream* stream, uint8_t id) { 5 | if (id < LOG_STREAM_MULTI_MAX_SIZE) { 6 | flush(); 7 | _streams[id] = stream; 8 | } 9 | } 10 | 11 | size_t LogStreamMulti::write(uint8_t character) { 12 | return write(&character, 1); 13 | } 14 | size_t LogStreamMulti::write(const uint8_t *buffer, size_t size) { 15 | size_t position = 0; 16 | 17 | if (_getBufferPosition() + size > LOG_STREAM_MULTI_BUFFER_SIZE - 1) { 18 | flush(); 19 | 20 | size_t part_size; 21 | while (size - position > LOG_STREAM_MULTI_BUFFER_SIZE - 1) { 22 | part_size = LOG_STREAM_MULTI_BUFFER_SIZE - 1; 23 | memcpy(&_buffer[0], buffer+position, part_size); 24 | position += part_size; 25 | flush(); 26 | } 27 | } 28 | memcpy(&_buffer[_getBufferPosition()], buffer+position, size-position); 29 | 30 | for (uint8_t i=position; iprintln();//("\r\n"); 46 | } 47 | flush(); 48 | return 2; 49 | } 50 | 51 | void LogStreamMulti::flush() { 52 | for (uint8_t i = 0; i < LOG_STREAM_MULTI_MAX_SIZE; i++) { 53 | if (_streams[i] == 0) 54 | continue; 55 | _streams[i]->write((const uint8_t*)&_buffer, _getBufferPosition()); 56 | _streams[i]->flush(); 57 | } 58 | memset(_buffer, '\0', LOG_STREAM_MULTI_BUFFER_SIZE); 59 | } 60 | 61 | size_t LogStreamMulti::_getBufferPosition() { 62 | return strlen((const char*)_buffer); 63 | } -------------------------------------------------------------------------------- /LogStreamMulti.h: -------------------------------------------------------------------------------- 1 | #ifndef LogStreamMulti_h 2 | #define LogStreamMulti_h 3 | 4 | #include 5 | 6 | #define LOG_STREAM_MULTI_MAX_SIZE 3 7 | #define LOG_STREAM_MULTI_BUFFER_SIZE 256 8 | 9 | class LogStreamMulti : public Stream { 10 | public: 11 | //LogStreamMulti(); 12 | void setSlot(Stream* stream, uint8_t id); 13 | 14 | size_t println(); 15 | 16 | size_t write(uint8_t character); 17 | size_t write(const uint8_t *buffer, size_t size); 18 | int available() { return 0; }; 19 | int read() { return 0; }; 20 | int peek() { return 0; }; 21 | void flush(); 22 | 23 | private: 24 | Stream* _streams[LOG_STREAM_MULTI_MAX_SIZE] = { }; 25 | uint8_t _buffer[LOG_STREAM_MULTI_BUFFER_SIZE] = { }; 26 | 27 | size_t _getBufferPosition(); 28 | 29 | }; 30 | #endif -------------------------------------------------------------------------------- /LogStreamSd.cpp: -------------------------------------------------------------------------------- 1 | #include "LogStreamSd.h" 2 | 3 | #include "WrapperWebServer.h" 4 | #include "Hackiebox.h" 5 | 6 | #include 7 | 8 | size_t LogStreamSd::write(uint8_t character) { 9 | return write(&character, 1); 10 | } 11 | size_t LogStreamSd::write(const uint8_t *buffer, size_t size) { 12 | if (!Box.boxSD.isInitialized()) 13 | return 0; 14 | if (!Config.get()->log.sdLog) 15 | return 0;/* 16 | if (!_isOpen) { 17 | Log.enableAdditionalLogger(false); 18 | Log.info("LogStreamSd::!_isOpen"); 19 | Log.enableAdditionalLogger(true); 20 | _isOpen = _file.open("/revvox/logging.log", FA_OPEN_APPEND | FA_WRITE); 21 | if (!_isOpen) 22 | return 0; 23 | }*/ 24 | _isOpen = _file.open("/revvox/logging.log", FA_OPEN_APPEND | FA_WRITE); 25 | if (!_isOpen) 26 | return 0; 27 | uint32_t result = _file.write((void*)buffer, (uint32_t)size); 28 | _file.close(); 29 | return result; 30 | } 31 | 32 | size_t LogStreamSd::println() { 33 | size_t result = print("\r\n"); 34 | /* 35 | if (_isOpen) { 36 | _file.close(); 37 | _isOpen = false; 38 | }*/ 39 | return result; 40 | } -------------------------------------------------------------------------------- /LogStreamSd.h: -------------------------------------------------------------------------------- 1 | #ifndef LogStreamSd_h 2 | #define LogStreamSd_h 3 | 4 | #include 5 | #include 6 | 7 | class LogStreamSd : public Stream { 8 | public: 9 | size_t println(); 10 | 11 | size_t write(uint8_t character); 12 | size_t write(const uint8_t *buffer, size_t size); 13 | int available() { return 0; }; 14 | int read() { return 0; }; 15 | int peek() { return 0; }; 16 | void flush() { }; 17 | 18 | private: 19 | FileFs _file; 20 | bool _isOpen = false; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /LogStreamSse.cpp: -------------------------------------------------------------------------------- 1 | #include "LogStreamSse.h" 2 | 3 | #include "Hackiebox.h" 4 | #include "WrapperWebServer.h" 5 | 6 | size_t LogStreamSse::write(uint8_t character) { 7 | if (Box.webServer.subscriptionCount == 0 || _ssePaused) 8 | return 0; 9 | 10 | for (uint8_t i = 0; i < SSE_MAX_CHANNELS; i++) { 11 | WrapperWebServer::SSESubscription* subscription = &Box.webServer.subscription[i]; 12 | WiFiClient* client = &(subscription->client); 13 | if (!(subscription->clientIP) || !client->connected()) 14 | continue; 15 | 16 | bool tagIsOpen = _tagIsOpen; 17 | if (character == '\n') { 18 | if (_tagIsOpen) { 19 | client->print("\" }\n\n"); // Extra newline required by SSE standard 20 | tagIsOpen = false; 21 | } 22 | } else { 23 | if (!_tagIsOpen) { 24 | client->print("data: { \"type\":\""); 25 | client->print("log"); 26 | client->print("\", \"data\":\""); 27 | tagIsOpen = true; 28 | } 29 | switch (character) { 30 | case '\r': 31 | case '\b': 32 | case '\f': 33 | break; 34 | case '\t': 35 | break; 36 | client->print("\\t"); 37 | case '\"': 38 | break; 39 | client->print("\\\""); 40 | default: 41 | client->print((char)character); 42 | break; 43 | } 44 | } 45 | _tagIsOpen = tagIsOpen; 46 | } 47 | return 1; 48 | } 49 | 50 | size_t LogStreamSse::println() { 51 | if (Box.webServer.subscriptionCount == 0 || _ssePaused) 52 | return 0; 53 | 54 | return print("\n"); 55 | } 56 | void LogStreamSse::setSsePaused(bool paused) { 57 | _ssePaused = paused; 58 | } -------------------------------------------------------------------------------- /LogStreamSse.h: -------------------------------------------------------------------------------- 1 | #ifndef LogStreamSse_h 2 | #define LogStreamSse_h 3 | 4 | #include 5 | 6 | class LogStreamSse : public Stream { 7 | public: 8 | size_t println(); 9 | 10 | size_t write(uint8_t); 11 | int available() { return 0; }; 12 | int read() { return 0; }; 13 | int peek() { return 0; }; 14 | void flush() { }; 15 | 16 | void setSsePaused(bool paused); 17 | 18 | private: 19 | bool _ssePaused = true; 20 | bool _tagIsOpen = false; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hackiebox_cfw 2 | Custom firmware for the Toniebox WIP 3 | 4 | **This firmware is dropped for favour of the new [HackieboxNG Custom Firmware project](https://github.com/toniebox-reverse-engineering/hackiebox_cfw_ng)** 5 | 6 | *** 7 | 8 | ## State of development 9 | The custom bootloader is fully functional. The hackiebox custom firmware itself isn't ready as daily driver yet. 10 | ### Implemented features: 11 | - Custom (sd) bootloader 12 | - Hardware drivers (WiFi, AMP/DAC, RFID reader, accelleration sensor, battery/charger, SD, LEDs) 13 | - Webinterface with basic CLI and file upload/download 14 | - JSON configuration file 15 | - Battery stamina test with WAV-playback with csv log 16 | - Tonie file header decoder 17 | - WAV player (with cli + by wav file in /rCONTENT//) 18 | ### Todo 19 | - [Configuration of the WiFi credentials via a WiFi Hotspot](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/issues/10) 20 | - [Optimize "Threading" to play WAV without stuttering](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/issues/14) 21 | - [OPUS decoding / playing tonie files](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/issues/12) 22 | - MP3 decoding (or other formats) 23 | - Remote WiFi speaker (chromecast or similar) 24 | - Custom (sd) bootloader that allows context sensitive firmware image switching for more complex features that don't fit into a single image 25 | - [Bootloader: Allow dynamic target address to allow direct starting of the OFW.](https://github.com/toniebox-reverse-engineering/hackiebox_cfw_ng) 26 | - [Bootloader: Dynamic patching of loaded firmwares by an addtitional file.](https://github.com/toniebox-reverse-engineering/hackiebox_cfw_ng) 27 | - [Overcome limited include path of Energia](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/issues/11) 28 | - [Set up buildchain without Energia / micropython dependencies](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/issues/13) 29 | ### Known bugs 30 | - WiFi configuration is cleared and replaced with the credentials from the json config## Installation 31 | ### Preface 32 | It is recommended to have a second copy of the cfw to be able to load the working image and update a broken cfw image over your backup cfw. 33 | ### Copy to sd 34 | First of all you need to create "/revvox/web" on your sd card (subdir revvox should be already there if you have successfully installed the sd bootloader) and copy over the content of the /web/ directory of this repository. The same applies to the "/revvox/audio" directory for WAV-playback during the battery test. In addition you have to copy your cfw image to your selected slot(s) on the sd card. (ex. "/revvox/boot/ng-cfw1.bin") 35 | ### First boot 36 | Reinsert the sd card and run the cfw once. Then shutdown the box again (put the box onto the front where the speaker/tonie logo is and press both ears for 10s). Then remove the sd card again and add your wifi credentials to the created "/revvox/hackiebox.config.json" config file. 37 | ## Firmware updates 38 | To update the firmware ota you can just use the hackiebox website und the box' ip address. (Expert->File Upload). 39 | ## Additional information for developers 40 | Keep in mind that connecting the RX Pin of the box to a serial interface blocks the big ear. For reading the log messages it is enough to have the TX Pin only connected (beside GND). 41 | In general there is something wrong with the firmware. If you start to refactor the firmware or make bigger changes, it may crash on startup. The reason is currently unknown. Could be a linker problem or some kind of overflow. 42 | 43 | ## Compiling 44 | ### Preface 45 | Building works on Windows, macOS and Linux. 46 | You should prepare the toniebox with the [sd bootloader](https://github.com/toniebox-reverse-engineering/hackiebox_cfw/wiki/Custom-bootloader) to load the cfw from your sd card. 47 | ### Prerequisite 48 | #### Energia 49 | Please install the latest [Energia release](https://energia.nu/download/) (1.8.10E23 tested). 50 | #### CC3200 board library 51 | In addition you need to install the cc3200 board library. 52 | #### Toniebox board library 53 | Replace the cc3200 board library files with the [toniebox-cc3200-core](https://github.com/toniebox-reverse-engineering/toniebox-cc3200-core) and restart energia. 54 | The folder with the boards packages are located at: 55 | ##### Windows 56 | `%LOCALAPPDATA%\Energia15\packages\hardware\` 57 | ##### Linux 58 | `~/.energia15/packages/energia/hardware/` 59 | ##### macOS 60 | `~/Library/Energia15/packages/hardware/` 61 | #### Additional libraries (Install lib via ZIP) 62 | [SimpleCLI](https://github.com/toniebox-reverse-engineering/SimpleCLI) 63 | [ESP8266Audio](https://github.com/toniebox-reverse-engineering/ESP8266Audio) 64 | [ESP8266SAM](https://github.com/toniebox-reverse-engineering/ESP8266SAM) 65 | ### Build 66 | Open hackiebox_cfw.ino with energia and build the cfw. Remember the path where the hackiebox.ino.bin is saved. Usally you find it at `C:\Users\\AppData\Local\Temp\arduino_build_XXXXXX`. 67 | 68 | -------------------------------------------------------------------------------- /TonieStructures.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/TonieStructures.h -------------------------------------------------------------------------------- /WrapperWebServer.h: -------------------------------------------------------------------------------- 1 | #ifndef WrapperWebServer_h 2 | #define WrapperWebServer_h 3 | 4 | #include "BaseHeader.h" 5 | #include 6 | #include 7 | #include "BoxTimer.h" 8 | 9 | #define SSE_MAX_CHANNELS 5 10 | 11 | class WrapperWebServer : public EnhancedThread { 12 | public: 13 | struct SSESubscription { 14 | IPAddress clientIP; 15 | WiFiClient client; 16 | //Ticker keepAliveTimer; 17 | } subscription[SSE_MAX_CHANNELS]; 18 | uint8_t subscriptionCount = 0; 19 | 20 | void 21 | begin(), 22 | loop(); 23 | 24 | void 25 | sendEvent(const char* eventname, const char* content, bool escapeData = true); 26 | //sendEventJSON(char* eventname, xyzjsondoc jsonContent) 27 | 28 | private: 29 | void 30 | handleNotFound(void), 31 | handleUnknown(void), 32 | handleRoot(void), 33 | handleFile(const char* path, const char* type), 34 | handleSseSub(void), 35 | handleAjax(void), 36 | handleUploadFile(void), 37 | handleUploadFlashFile(void); 38 | 39 | void 40 | sendJsonSuccess(); 41 | 42 | void 43 | sseHandler(uint8_t channel), 44 | sseKeepAlive(); 45 | 46 | 47 | bool 48 | commandGetFile(String* path, uint32_t read_start, uint32_t read_length, bool download), 49 | commandGetFlashFile(String* path, uint32_t read_start, uint32_t read_length); 50 | 51 | BoxTimer _sseTimer; 52 | WebServer* _server; 53 | FileFs _uploadFile; 54 | bool _uploadFileOpen = false; 55 | bool _uploadFlashFileOpen = false; 56 | 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /WrapperWiFi.cpp: -------------------------------------------------------------------------------- 1 | #include "WrapperWiFi.h" 2 | #include "BoxEvents.h" 3 | 4 | WrapperWiFi::WrapperWiFi(const char* ssid, const char* password) { 5 | _ssid = ssid; 6 | _password = password; 7 | byte empty[4] = {0}; 8 | memcpy(_ip, empty, sizeof(_ip)); 9 | memcpy(_subnet, empty, sizeof(_subnet)); 10 | memcpy(_dns, empty, sizeof(_dns)); 11 | } 12 | 13 | WrapperWiFi::WrapperWiFi(const char* ssid, const char* password, const byte ip[4], const byte subnet[4], const byte dns[4]) { 14 | _ssid = ssid; 15 | _password = password; 16 | if (ip[0] != 0) { 17 | memcpy(_ip, ip, sizeof(_ip)); 18 | memcpy(_subnet, subnet, sizeof(_subnet)); 19 | memcpy(_dns, dns, sizeof(_dns)); 20 | } else { 21 | byte empty[4] = {0}; 22 | memcpy(_ip, empty, sizeof(_ip)); 23 | memcpy(_subnet, empty, sizeof(_subnet)); 24 | memcpy(_dns, empty, sizeof(_dns)); 25 | } 26 | } 27 | 28 | void WrapperWiFi::begin() { 29 | WiFiClass::WlanProfile profiles[8]; 30 | WiFi.getSavedProfiles(profiles); 31 | 32 | Log.info("Known WiFi Profiles:"); 33 | for (uint8_t i=0; i<7; i++) { 34 | if (profiles[i].wifiNameLen>0) { 35 | profiles[i].wifiName[profiles[i].wifiNameLen] = '\0'; 36 | Log.info(" -%i: Name=%s", i, profiles[i].wifiName); 37 | } 38 | } 39 | 40 | setInterval(5000); 41 | reconnect(); 42 | } 43 | 44 | void WrapperWiFi::loop() { 45 | switch (_state) { 46 | case ConnectionState::WAIT_CONNECT: 47 | //TODO: CHECK FOR TIMEOUT / GOTO AP Mode 48 | if (WiFi.status() == WL_CONNECTED) { 49 | _state = ConnectionState::WAIT_IP; 50 | Events.handleWiFiEvent(_state); 51 | setInterval(100); 52 | } 53 | break; 54 | case ConnectionState::WAIT_IP: 55 | if (!(WiFi.localIP() == INADDR_NONE)) { 56 | _state = ConnectionState::CONNECTED; 57 | Events.handleWiFiEvent(_state); 58 | setInterval(5000); 59 | } 60 | break; 61 | case ConnectionState::CONNECTED: 62 | if (WiFi.status() != WL_CONNECTED) { 63 | _state = ConnectionState::DISCONNECTED; 64 | Events.handleWiFiEvent(_state); 65 | setInterval(5000); 66 | } 67 | default: 68 | break; 69 | } 70 | 71 | } 72 | 73 | void WrapperWiFi::reconnect() { //TODO: LED Stuff 74 | _state = ConnectionState::NONE; 75 | Log.debug("WrapperWiFi(ssid=\"%s\", password=\"%s\")", _ssid, _password); 76 | 77 | Log.info("Connect to WiFi %s", _ssid); 78 | 79 | //stationary mode 80 | if (_ip[0] != 0) { 81 | Log.info(" using static ip"); 82 | WiFi.config(_ip, _dns, _subnet); 83 | } else { 84 | Log.info(" using dynamic ip"); 85 | } 86 | 87 | WiFi.begin((char*)_ssid, (char*)_password); 88 | _state = ConnectionState::WAIT_CONNECT; 89 | Events.handleWiFiEvent(_state); 90 | } 91 | 92 | void WrapperWiFi::apMode() { 93 | WiFi.beginNetwork("Hackiebox-"); 94 | } 95 | 96 | WrapperWiFi::ConnectionState WrapperWiFi::getStatus() { 97 | return _state; 98 | } 99 | 100 | void WrapperWiFi::mDnsAdvertiseSetup() { 101 | const char* service = "Hackiebox._api._tcp.local"; //max 64 bytes? 102 | const char* text = "Hackiebox"; 103 | 104 | int16_t result = sl_NetAppMDNSUnRegisterService(0, 0); 105 | 106 | if (result != 0) 107 | Log.error("mDNS service unreg 1 failed=%i", result); 108 | 109 | //Registering for the mDNS service. 110 | result = sl_NetAppMDNSUnRegisterService( 111 | (const signed char*)service, 112 | (const unsigned char)strlen(service) 113 | ); 114 | 115 | if (result != 0 && result != SL_NET_APP_DNS_NOT_EXISTED_SERVICE_ERROR) 116 | Log.error("mDNS service unreg 2 failed=%i", result); 117 | 118 | result = sl_NetAppMDNSRegisterService( 119 | (const signed char*)service, //Service 120 | (const unsigned char)strlen(service), //ServiceLen 121 | (const signed char*)text, //Text 122 | (const unsigned char)strlen(text), //TextLen 123 | 80, //Port 124 | 2000, //TTL 125 | 1 //Options 126 | ); 127 | 128 | if(result == 0) { 129 | Log.info("mDNS service %s with %s enabled", service, text); 130 | } else { 131 | Log.error("mDNS service setup failed=%i", result); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /WrapperWiFi.h: -------------------------------------------------------------------------------- 1 | #ifndef WrapperWiFi_h 2 | #define WrapperWiFi_h 3 | 4 | #include 5 | 6 | #include "BaseHeader.h" 7 | 8 | #include 9 | 10 | class WrapperWiFi : public EnhancedThread { 11 | public: 12 | enum class ConnectionState { 13 | NONE, 14 | WAIT_CONNECT, 15 | WAIT_IP, 16 | CONNECTED, 17 | DISCONNECTED 18 | }; 19 | 20 | WrapperWiFi() {}; 21 | WrapperWiFi(const char* ssid, const char* password); 22 | WrapperWiFi(const char* ssid, const char* password, const byte ip[4], const byte subnet[4], const byte dns[4]); 23 | 24 | void 25 | begin(), 26 | loop(); 27 | 28 | void 29 | reconnect(); 30 | 31 | void apMode(); 32 | 33 | WrapperWiFi::ConnectionState getStatus(); 34 | 35 | void mDnsAdvertiseSetup(); 36 | 37 | private: 38 | const char* _ssid; 39 | const char* _password; 40 | byte _ip[4]; 41 | byte _subnet[4]; 42 | byte _dns[4]; 43 | 44 | ConnectionState _state = ConnectionState::NONE; 45 | }; 46 | 47 | #endif -------------------------------------------------------------------------------- /audio/both-of-us-14037.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/both-of-us-14037.wav -------------------------------------------------------------------------------- /audio/cinematic-fairy-tale-story-main-8697.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/cinematic-fairy-tale-story-main-8697.wav -------------------------------------------------------------------------------- /audio/electronic-rock-king-around-here-15045.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/electronic-rock-king-around-here-15045.wav -------------------------------------------------------------------------------- /audio/into-the-night-20928.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/into-the-night-20928.wav -------------------------------------------------------------------------------- /audio/melody-of-nature-main-6672.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/melody-of-nature-main-6672.wav -------------------------------------------------------------------------------- /audio/nightlife-michael-kobrin-95bpm-3783.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/nightlife-michael-kobrin-95bpm-3783.wav -------------------------------------------------------------------------------- /audio/source/both-of-us-14037.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schlagt-both-of-us-14037/ 2 | -------------------------------------------------------------------------------- /audio/source/cinematic-fairy-tale-story-main-8697.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/uberschrift-cinematic-fairy-tale-story-main-8697/ 2 | -------------------------------------------------------------------------------- /audio/source/electronic-rock-king-around-here-15045.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schlagt-electronic-rock-king-around-here-15045/ 2 | -------------------------------------------------------------------------------- /audio/source/into-the-night-20928.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schlagt-into-the-night-20928/ 2 | -------------------------------------------------------------------------------- /audio/source/melody-of-nature-main-6672.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schone-stucke-melody-of-nature-main-6672/ 2 | -------------------------------------------------------------------------------- /audio/source/nightlife-michael-kobrin-95bpm-3783.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/akustische-gruppe-nightlife-michael-kobrin-95bpm-3783/ 2 | -------------------------------------------------------------------------------- /audio/source/the-cradle-of-your-soul-15700.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/solo-gitarre-the-cradle-of-your-soul-15700/ 2 | -------------------------------------------------------------------------------- /audio/source/trailer-sport-stylish-16073.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schlagt-trailer-sport-stylish-16073/ 2 | -------------------------------------------------------------------------------- /audio/source/yesterday-extended-version-14197.txt: -------------------------------------------------------------------------------- 1 | https://pixabay.com/de/music/schlagt-yesterday-extended-version-14197/ 2 | -------------------------------------------------------------------------------- /audio/the-cradle-of-your-soul-15700.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/the-cradle-of-your-soul-15700.wav -------------------------------------------------------------------------------- /audio/trailer-sport-stylish-16073.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/trailer-sport-stylish-16073.wav -------------------------------------------------------------------------------- /audio/yesterday-extended-version-14197.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toniebox-reverse-engineering/hackiebox_cfw/7574a117f050bd1b821265f096be4e46c376aaaa/audio/yesterday-extended-version-14197.wav -------------------------------------------------------------------------------- /hackiebox_cfw.ino: -------------------------------------------------------------------------------- 1 | #include "Hackiebox.h" 2 | 3 | 4 | void setup() { 5 | Box.setup(); 6 | } 7 | 8 | void loop() { 9 | Box.loop(); 10 | } 11 | -------------------------------------------------------------------------------- /web/local-echo.js: -------------------------------------------------------------------------------- 1 | var LocalEchoController=function(t){var e={};function r(i){if(e[i])return e[i].exports;var n=e[i]={i:i,l:!1,exports:{}};return t[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=t,r.c=e,r.d=function(t,e,i){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)r.d(i,n,function(e){return t[e]}.bind(null,n));return i},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=1)}([function(t,e){e.quote=function(t){return t.map((function(t){return t&&"object"==typeof t?t.op.replace(/(.)/g,"\\$1"):/["\s]/.test(t)&&!/'/.test(t)?"'"+t.replace(/(['\\])/g,"\\$1")+"'":/["'\s]/.test(t)?'"'+t.replace(/(["\\$`!])/g,"\\$1")+'"':String(t).replace(/([A-z]:)?([#!"$&'()*,:;<=>?@\[\\\]^`{|}])/g,"$1\\$2")})).join(" ")};for(var r="(?:"+["\\|\\|","\\&\\&",";;","\\|\\&","\\<\\(",">>",">\\&","[&;()|<>]"].join("|")+")",i="",n=0;n<4;n++)i+=(Math.pow(16,8)*Math.random()).toString(16);e.parse=function(t,e,n){var s=function(t,e,n){var s=new RegExp(["("+r+")","((\\\\['\"|&;()<> \\t]|[^\\s'\"|&;()<> \\t])+|\"((\\\\\"|[^\"])*?)\"|'((\\\\'|[^'])*?)')*"].join("|"),"g"),o=t.match(s).filter(Boolean),a=!1;if(!o)return[];e||(e={});n||(n={});return o.map((function(t,s){if(!a){if(RegExp("^"+r+"$").test(t))return{op:t};for(var u=n.escape||"\\",h=!1,l=!1,c="",p=!1,f=0,v=t.length;fthis.size&&this.entries.pop(0),this.cursor=this.entries.length)}},{key:"rewind",value:function(){this.cursor=this.entries.length}},{key:"getPrevious",value:function(){var t=Math.max(0,this.cursor-1);return this.cursor=t,this.entries[t]}},{key:"getNext",value:function(){var t=Math.min(this.entries.length,this.cursor+1);return this.cursor=t,this.entries[t]}}])&&i(e.prototype,r),n&&i(e,n),t}(),s=r(0);function o(t){return function(t){if(Array.isArray(t))return a(t)}(t)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return a(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);"Object"===r&&t.constructor&&(r=t.constructor.name);if("Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return a(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,i=new Array(e);r1&&void 0!==arguments[1])||arguments[1],i=[],n=/\w+/g;e=n.exec(t);)r?i.push(e.index):i.push(e.index+e[0].length);return i}function h(t,e){var r=u(t,!0).reverse().find((function(t){return tr)&&(n=0,i+=1)}return{row:i,col:n}}function c(t,e){return l(t,t.length,e).row+1}function p(t){return null!=t.match(/[^\\][ \t]$/m)}function f(t){return""===t.trim()||p(t)?"":Object(s.parse)(t).pop()||""}function v(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function m(t,e){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:null,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};v(this,t),this.term=e,this._handleTermData=this.handleTermData.bind(this),this._handleTermResize=this.handleTermResize.bind(this),this.history=new n(r.historySize||10),this.maxAutocompleteEntries=r.maxAutocompleteEntries||100,this._autocompleteHandlers=[],this._active=!1,this._input="",this._cursor=0,this._activePrompt=null,this._activeCharPrompt=null,this._termSize={cols:0,rows:0},this._disposables=[],e&&(e.loadAddon?e.loadAddon(this):this.attach())}var e,r,i;return e=t,(r=[{key:"activate",value:function(t){this.term=t,this.attach()}},{key:"dispose",value:function(){this.detach()}},{key:"detach",value:function(){this.term.off?(this.term.off("data",this._handleTermData),this.term.off("resize",this._handleTermResize)):(this._disposables.forEach((function(t){return t.dispose()})),this._disposables=[])}},{key:"attach",value:function(){this.term.on?(this.term.on("data",this._handleTermData),this.term.on("resize",this._handleTermResize)):(this._disposables.push(this.term.onData(this._handleTermData)),this._disposables.push(this.term.onResize(this._handleTermResize))),this._termSize={cols:this.term.cols,rows:this.term.rows}}},{key:"addAutocompleteHandler",value:function(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:"> ";return new Promise((function(i,n){e.term.write(t),e._activePrompt={prompt:t,continuationPrompt:r,resolve:i,reject:n},e._input="",e._cursor=0,e._active=!0}))}},{key:"readChar",value:function(t){var e=this;return new Promise((function(r,i){e.term.write(t),e._activeCharPrompt={prompt:t,resolve:r,reject:i}}))}},{key:"abortRead",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"aborted";null==this._activePrompt&&null==this._activeCharPrompt||this.term.write("\r\n"),null!=this._activePrompt&&(this._activePrompt.reject(t),this._activePrompt=null),null!=this._activeCharPrompt&&(this._activeCharPrompt.reject(t),this._activeCharPrompt=null),this._active=!1}},{key:"println",value:function(t){this.print(t+"\n")}},{key:"print",value:function(t){var e=t.replace(/[\r\n]+/g,"\n");this.term.write(e.replace(/\n/g,"\r\n"))}},{key:"printWide",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:2;if(0==t.length)return println("");for(var r=t.reduce((function(t,e){return Math.max(t,e.length)}),0)+e,i=Math.floor(this._termSize.cols/r),n=Math.ceil(t.length/i),s=0,o=0;o1&&void 0!==arguments[1])||arguments[1];e&&this.clearInput();var r=this.applyPrompts(t);this.print(r),this._cursor>t.length&&(this._cursor=t.length);var i=this.applyPromptOffset(t,this._cursor),n=c(r,this._termSize.cols),s=l(r,i,this._termSize.cols),o=s.col,a=s.row,u=n-a-1;this.term.write("\r");for(var h=0;hthis._input.length&&(t=this._input.length);var e=this.applyPrompts(this._input),r=(c(e,this._termSize.cols),l(e,this.applyPromptOffset(this._input,this._cursor),this._termSize.cols)),i=r.col,n=r.row,s=l(e,this.applyPromptOffset(this._input,t),this._termSize.cols),o=s.col,a=s.row;if(a>n)for(var u=n;ui)for(var p=i;p0){var e=Math.min(t,this._input.length-this._cursor);this.setCursor(this._cursor+e)}else if(t<0){var r=Math.max(t,-this._cursor);this.setCursor(this._cursor+r)}}},{key:"handleCursorErase",value:function(t){var e=this._cursor,r=this._input;if(t){if(e<=0)return;var i=r.substr(0,e-1)+r.substr(e);this.clearInput(),this._cursor-=1,this.setInput(i,!1)}else{var n=r.substr(0,e)+r.substr(e+1);this.setInput(n)}}},{key:"handleCursorInsert",value:function(t){var e=this._cursor,r=this._input,i=r.substr(0,e)+t+r.substr(e);this._cursor+=t.length,this.setInput(i)}},{key:"handleReadComplete",value:function(){this.history&&this.history.push(this._input),this._activePrompt&&(this._activePrompt.resolve(this._input),this._activePrompt=null),this.term.write("\r\n"),this._active=!1}},{key:"handleTermResize",value:function(t){var e=t.rows,r=t.cols;this.clearInput(),this._termSize={cols:r,rows:e},this.setInput(this._input,!1)}},{key:"handleTermData",value:function(t){var e=this;if(this._active){if(null!=this._activeCharPrompt)return this._activeCharPrompt.resolve(t),this._activeCharPrompt=null,void this.term.write("\r\n");if(t.length>3&&27!==t.charCodeAt(0)){var r=t.replace(/[\r\n]+/g,"\r");Array.from(r).forEach((function(t){return e.handleData(t)}))}else this.handleData(t)}}},{key:"handleData",value:function(t){var e=this;if(this._active){var r,i,n,a,l=t.charCodeAt(0);if(27==l)switch(t.substr(1)){case"[A":if(this.history){var c=this.history.getPrevious();c&&(this.setInput(c),this.setCursor(c.length))}break;case"[B":if(this.history){var v=this.history.getNext();v||(v=""),this.setInput(v),this.setCursor(v.length)}break;case"[D":this.handleCursorMove(-1);break;case"[C":this.handleCursorMove(1);break;case"[3~":this.handleCursorErase(!1);break;case"[F":this.setCursor(this._input.length);break;case"[H":this.setCursor(0);break;case"b":null!=(r=h(this._input,this._cursor))&&this.setCursor(r);break;case"f":i=this._input,n=this._cursor,null!=(r=null==(a=u(i,!1).find((function(t){return t>n})))?i.length:a)&&this.setCursor(r);break;case"":null!=(r=h(this._input,this._cursor))&&(this.setInput(this._input.substr(0,r)+this._input.substr(this._cursor)),this.setCursor(r))}else if(l<32||127===l)switch(t){case"\r":!function(t){return""!=t.trim()&&((t.match(/'/g)||[]).length%2!=0||(t.match(/"/g)||[]).length%2!=0||""==t.split(/(\|\||\||&&)/g).pop().trim()||!(!t.endsWith("\\")||t.endsWith("\\\\")))}(this._input)?this.handleReadComplete():this.handleCursorInsert("\n");break;case"":this.handleCursorErase(!0);break;case"\t":if(this._autocompleteHandlers.length>0){var m=this._input.substr(0,this._cursor),d=p(m),_=function(t,e){var r=Object(s.parse)(e),i=r.length-1,n=r[i]||"";return""===e.trim()?(i=0,n=""):p(e)&&(i+=1,n=""),t.reduce((function(t,e){var n=e.fn,s=e.args;try{return t.concat(n.apply(void 0,[i,r].concat(o(s))))}catch(e){return console.error("Auto-complete error:",e),t}}),[]).filter((function(t){return t.startsWith(n)}))}(this._autocompleteHandlers,m);if(_.sort(),0===_.length)d||this.handleCursorInsert(" ");else if(1===_.length){var g=f(m);this.handleCursorInsert(_[0].substr(g.length)+" ")}else if(_.length<=this.maxAutocompleteEntries){var y=function t(e,r){if(e.length>=r[0].length)return e;var i=e;e+=r[0].slice(e.length,e.length+1);for(var n=0;n