mirror of
https://github.com/xfjx/TonUINO.git
synced 2025-08-22 15:53:43 +02:00
588 lines
17 KiB
C++
588 lines
17 KiB
C++
#include <DFMiniMp3.h>
|
|
#include <EEPROM.h>
|
|
#include <JC_Button.h>
|
|
#include <MFRC522.h>
|
|
#include <SPI.h>
|
|
#include <SoftwareSerial.h>
|
|
|
|
// DFPlayer Mini
|
|
SoftwareSerial mySoftwareSerial(2, 3); // RX, TX
|
|
uint16_t numTracksInFolder;
|
|
uint16_t currentTrack;
|
|
|
|
// this object stores nfc tag data
|
|
struct nfcTagObject {
|
|
uint32_t cookie;
|
|
uint8_t version;
|
|
uint8_t folder;
|
|
uint8_t mode;
|
|
uint8_t special;
|
|
};
|
|
|
|
nfcTagObject myCard;
|
|
|
|
static void nextTrack(uint16_t track);
|
|
int voiceMenu(int numberOfOptions, int startMessage, int messageOffset,
|
|
bool preview = false, int previewFromFolder = 0);
|
|
|
|
bool knownCard = false;
|
|
|
|
// implement a notification class,
|
|
// its member methods will get called
|
|
//
|
|
class Mp3Notify {
|
|
public:
|
|
static void OnError(uint16_t errorCode) {
|
|
// see DfMp3_Error for code meaning
|
|
Serial.println();
|
|
Serial.print("Com Error ");
|
|
Serial.println(errorCode);
|
|
}
|
|
static void OnPlayFinished(uint16_t track) {
|
|
Serial.print("Track beendet");
|
|
Serial.println(track);
|
|
delay(100);
|
|
nextTrack(track);
|
|
}
|
|
static void OnCardOnline(uint16_t code) {
|
|
Serial.println(F("SD Karte online "));
|
|
}
|
|
static void OnCardInserted(uint16_t code) {
|
|
Serial.println(F("SD Karte bereit "));
|
|
}
|
|
static void OnCardRemoved(uint16_t code) {
|
|
Serial.println(F("SD Karte entfernt "));
|
|
}
|
|
static void OnUsbOnline(uint16_t code) {
|
|
Serial.println(F("USB online "));
|
|
}
|
|
static void OnUsbInserted(uint16_t code) {
|
|
Serial.println(F("USB bereit "));
|
|
}
|
|
static void OnUsbRemoved(uint16_t code) {
|
|
Serial.println(F("USB entfernt "));
|
|
}
|
|
};
|
|
|
|
static DFMiniMp3<SoftwareSerial, Mp3Notify> mp3(mySoftwareSerial);
|
|
|
|
// Leider kann das Modul keine Queue abspielen.
|
|
static uint16_t _lastTrackFinished;
|
|
static void nextTrack(uint16_t track) {
|
|
if (track == _lastTrackFinished) {
|
|
return;
|
|
}
|
|
_lastTrackFinished = track;
|
|
|
|
if (knownCard == false)
|
|
// Wenn eine neue Karte angelernt wird soll das Ende eines Tracks nicht
|
|
// verarbeitet werden
|
|
return;
|
|
|
|
if (myCard.mode == 1) {
|
|
Serial.println(F("Hörspielmodus ist aktiv -> keinen neuen Track spielen"));
|
|
// mp3.sleep(); // Je nach Modul kommt es nicht mehr zurück aus dem Sleep!
|
|
}
|
|
if (myCard.mode == 2) {
|
|
if (currentTrack != numTracksInFolder) {
|
|
currentTrack = currentTrack + 1;
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
Serial.print(F("Albummodus ist aktiv -> nächster Track: "));
|
|
Serial.print(currentTrack);
|
|
} else
|
|
// mp3.sleep(); // Je nach Modul kommt es nicht mehr zurück aus dem Sleep!
|
|
{ }
|
|
}
|
|
if (myCard.mode == 3) {
|
|
uint16_t oldTrack = currentTrack;
|
|
currentTrack = random(1, numTracksInFolder + 1);
|
|
if (currentTrack == oldTrack)
|
|
currentTrack = currentTrack == numTracksInFolder ? 1 : currentTrack+1;
|
|
Serial.print(F("Party Modus ist aktiv -> zufälligen Track spielen: "));
|
|
Serial.println(currentTrack);
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
if (myCard.mode == 4) {
|
|
Serial.println(F("Einzel Modus aktiv -> Strom sparen"));
|
|
// mp3.sleep(); // Je nach Modul kommt es nicht mehr zurück aus dem Sleep!
|
|
}
|
|
if (myCard.mode == 5) {
|
|
if (currentTrack != numTracksInFolder) {
|
|
currentTrack = currentTrack + 1;
|
|
Serial.print(F("Hörbuch Modus ist aktiv -> nächster Track und "
|
|
"Fortschritt speichern"));
|
|
Serial.println(currentTrack);
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
// Fortschritt im EEPROM abspeichern
|
|
EEPROM.write(myCard.folder, currentTrack);
|
|
} else {
|
|
// mp3.sleep(); // Je nach Modul kommt es nicht mehr zurück aus dem Sleep!
|
|
// Fortschritt zurück setzen
|
|
EEPROM.write(myCard.folder, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void previousTrack() {
|
|
if (myCard.mode == 1) {
|
|
Serial.println(F("Hörspielmodus ist aktiv -> Track von vorne spielen"));
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
if (myCard.mode == 2) {
|
|
Serial.println(F("Albummodus ist aktiv -> vorheriger Track"));
|
|
if (currentTrack != 1) {
|
|
currentTrack = currentTrack - 1;
|
|
}
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
if (myCard.mode == 3) {
|
|
Serial.println(F("Party Modus ist aktiv -> Track von vorne spielen"));
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
if (myCard.mode == 4) {
|
|
Serial.println(F("Einzel Modus aktiv -> Track von vorne spielen"));
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
if (myCard.mode == 5) {
|
|
Serial.println(F("Hörbuch Modus ist aktiv -> vorheriger Track und "
|
|
"Fortschritt speichern"));
|
|
if (currentTrack != 1) {
|
|
currentTrack = currentTrack - 1;
|
|
}
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
// Fortschritt im EEPROM abspeichern
|
|
EEPROM.write(myCard.folder, currentTrack);
|
|
}
|
|
}
|
|
|
|
// MFRC522
|
|
#define RST_PIN 9 // Configurable, see typical pin layout above
|
|
#define SS_PIN 10 // Configurable, see typical pin layout above
|
|
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522
|
|
MFRC522::MIFARE_Key key;
|
|
bool successRead;
|
|
byte sector = 1;
|
|
byte blockAddr = 4;
|
|
byte trailerBlock = 7;
|
|
MFRC522::StatusCode status;
|
|
|
|
#define buttonPause A0
|
|
#define buttonUp A1
|
|
#define buttonDown A2
|
|
#define busyPin 4
|
|
|
|
#define LONG_PRESS 1000
|
|
|
|
Button pauseButton(buttonPause);
|
|
Button upButton(buttonUp);
|
|
Button downButton(buttonDown);
|
|
bool ignorePauseButton = false;
|
|
bool ignoreUpButton = false;
|
|
bool ignoreDownButton = false;
|
|
|
|
uint8_t numberOfCards = 0;
|
|
|
|
bool isPlaying() { return !digitalRead(busyPin); }
|
|
|
|
void setup() {
|
|
|
|
Serial.begin(115200); // Es gibt ein paar Debug Ausgaben über die serielle
|
|
// Schnittstelle
|
|
randomSeed(analogRead(A0)); // Zufallsgenerator initialisieren
|
|
|
|
Serial.println(F("TonUINO Version 2.0"));
|
|
Serial.println(F("(c) Thorsten Voß"));
|
|
|
|
// Knöpfe mit PullUp
|
|
pinMode(buttonPause, INPUT_PULLUP);
|
|
pinMode(buttonUp, INPUT_PULLUP);
|
|
pinMode(buttonDown, INPUT_PULLUP);
|
|
|
|
// Busy Pin
|
|
pinMode(busyPin, INPUT);
|
|
|
|
// DFPlayer Mini initialisieren
|
|
mp3.begin();
|
|
mp3.setVolume(15);
|
|
|
|
// NFC Leser initialisieren
|
|
SPI.begin(); // Init SPI bus
|
|
mfrc522.PCD_Init(); // Init MFRC522
|
|
mfrc522
|
|
.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader
|
|
for (byte i = 0; i < 6; i++) {
|
|
key.keyByte[i] = 0xFF;
|
|
}
|
|
|
|
// RESET --- ALLE DREI KNÖPFE BEIM STARTEN GEDRÜCKT HALTEN -> alle bekannten
|
|
// Karten werden gelöscht
|
|
if (digitalRead(buttonPause) == LOW && digitalRead(buttonUp) == LOW &&
|
|
digitalRead(buttonDown) == LOW) {
|
|
Serial.println(F("Reset -> EEPROM wird gelöscht"));
|
|
for (int i = 0; i < EEPROM.length(); i++) {
|
|
EEPROM.write(i, 0);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void loop() {
|
|
do {
|
|
mp3.loop();
|
|
// Buttons werden nun über JS_Button gehandelt, dadurch kann jede Taste
|
|
// doppelt belegt werden
|
|
pauseButton.read();
|
|
upButton.read();
|
|
downButton.read();
|
|
|
|
if (pauseButton.wasReleased()) {
|
|
if (ignorePauseButton == false) {
|
|
if (isPlaying())
|
|
mp3.pause();
|
|
else
|
|
mp3.start();
|
|
}
|
|
ignorePauseButton = false;
|
|
} else if (pauseButton.pressedFor(LONG_PRESS) &&
|
|
ignorePauseButton == false) {
|
|
if (isPlaying())
|
|
mp3.playAdvertisement(currentTrack);
|
|
else {
|
|
knownCard = false;
|
|
mp3.playMp3FolderTrack(800);
|
|
Serial.println(F("Karte resetten..."));
|
|
resetCard();
|
|
mfrc522.PICC_HaltA();
|
|
mfrc522.PCD_StopCrypto1();
|
|
}
|
|
ignorePauseButton = true;
|
|
}
|
|
|
|
if (upButton.pressedFor(LONG_PRESS)) {
|
|
Serial.println(F("Volume Up"));
|
|
mp3.increaseVolume();
|
|
ignoreUpButton = true;
|
|
} else if (upButton.wasReleased()) {
|
|
if (!ignoreUpButton)
|
|
nextTrack(random(65536));
|
|
else
|
|
ignoreUpButton = false;
|
|
}
|
|
|
|
if (downButton.pressedFor(LONG_PRESS)) {
|
|
Serial.println(F("Volume Down"));
|
|
mp3.decreaseVolume();
|
|
ignoreDownButton = true;
|
|
} else if (downButton.wasReleased()) {
|
|
if (!ignoreDownButton)
|
|
previousTrack();
|
|
else
|
|
ignoreDownButton = false;
|
|
}
|
|
// Ende der Buttons
|
|
} while (!mfrc522.PICC_IsNewCardPresent());
|
|
|
|
// RFID Karte wurde aufgelegt
|
|
|
|
if (!mfrc522.PICC_ReadCardSerial())
|
|
return;
|
|
|
|
if (readCard(&myCard) == true) {
|
|
if (myCard.cookie == 322417479 && myCard.folder != 0 && myCard.mode != 0) {
|
|
|
|
knownCard = true;
|
|
_lastTrackFinished = 0;
|
|
numTracksInFolder = mp3.getFolderTrackCount(myCard.folder);
|
|
Serial.print(numTracksInFolder);
|
|
Serial.print(F(" Dateien in Ordner "));
|
|
Serial.println(myCard.folder);
|
|
|
|
// Hörspielmodus: eine zufällige Datei aus dem Ordner
|
|
if (myCard.mode == 1) {
|
|
Serial.println(F("Hörspielmodus -> zufälligen Track wiedergeben"));
|
|
currentTrack = random(1, numTracksInFolder + 1);
|
|
Serial.println(currentTrack);
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
// Album Modus: kompletten Ordner spielen
|
|
if (myCard.mode == 2) {
|
|
Serial.println(F("Album Modus -> kompletten Ordner wiedergeben"));
|
|
currentTrack = 1;
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
// Party Modus: Ordner in zufälliger Reihenfolge
|
|
if (myCard.mode == 3) {
|
|
Serial.println(
|
|
F("Party Modus -> Ordner in zufälliger Reihenfolge wiedergeben"));
|
|
currentTrack = random(1, numTracksInFolder + 1);
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
// Einzel Modus: eine Datei aus dem Ordner abspielen
|
|
if (myCard.mode == 4) {
|
|
Serial.println(
|
|
F("Einzel Modus -> eine Datei aus dem Odrdner abspielen"));
|
|
currentTrack = myCard.special;
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
// Hörbuch Modus: kompletten Ordner spielen und Fortschritt merken
|
|
if (myCard.mode == 5) {
|
|
Serial.println(F("Hörbuch Modus -> kompletten Ordner spielen und "
|
|
"Fortschritt merken"));
|
|
currentTrack = EEPROM.read(myCard.folder);
|
|
mp3.playFolderTrack(myCard.folder, currentTrack);
|
|
}
|
|
}
|
|
|
|
// Neue Karte konfigurieren
|
|
else {
|
|
knownCard = false;
|
|
setupCard();
|
|
}
|
|
}
|
|
mfrc522.PICC_HaltA();
|
|
mfrc522.PCD_StopCrypto1();
|
|
}
|
|
|
|
int voiceMenu(int numberOfOptions, int startMessage, int messageOffset,
|
|
bool preview = false, int previewFromFolder = 0) {
|
|
int returnValue = 0;
|
|
if (startMessage != 0)
|
|
mp3.playMp3FolderTrack(startMessage);
|
|
do {
|
|
pauseButton.read();
|
|
upButton.read();
|
|
downButton.read();
|
|
mp3.loop();
|
|
if (pauseButton.wasPressed()) {
|
|
if (returnValue != 0)
|
|
return returnValue;
|
|
delay(1000);
|
|
}
|
|
|
|
if (upButton.pressedFor(LONG_PRESS)) {
|
|
returnValue = min(returnValue + 10, numberOfOptions);
|
|
mp3.playMp3FolderTrack(messageOffset + returnValue);
|
|
delay(1000);
|
|
if (preview) {
|
|
do {
|
|
delay(10);
|
|
} while (isPlaying());
|
|
if (previewFromFolder == 0)
|
|
mp3.playFolderTrack(returnValue, 1);
|
|
else
|
|
mp3.playFolderTrack(previewFromFolder, returnValue);
|
|
}
|
|
ignoreUpButton = true;
|
|
} else if (upButton.wasReleased()) {
|
|
if (!ignoreUpButton) {
|
|
returnValue = min(returnValue + 1, numberOfOptions);
|
|
mp3.playMp3FolderTrack(messageOffset + returnValue);
|
|
delay(1000);
|
|
if (preview) {
|
|
do {
|
|
delay(10);
|
|
} while (isPlaying());
|
|
if (previewFromFolder == 0)
|
|
mp3.playFolderTrack(returnValue, 1);
|
|
else
|
|
mp3.playFolderTrack(previewFromFolder, returnValue);
|
|
}
|
|
} else
|
|
ignoreUpButton = false;
|
|
}
|
|
|
|
if (downButton.pressedFor(LONG_PRESS)) {
|
|
returnValue = max(returnValue - 10, 1);
|
|
mp3.playMp3FolderTrack(messageOffset + returnValue);
|
|
delay(1000);
|
|
if (preview) {
|
|
do {
|
|
delay(10);
|
|
} while (isPlaying());
|
|
if (previewFromFolder == 0)
|
|
mp3.playFolderTrack(returnValue, 1);
|
|
else
|
|
mp3.playFolderTrack(previewFromFolder, returnValue);
|
|
}
|
|
ignoreDownButton = true;
|
|
} else if (downButton.wasReleased()) {
|
|
if (!ignoreDownButton) {
|
|
returnValue = max(returnValue - 1, 1);
|
|
mp3.playMp3FolderTrack(messageOffset + returnValue);
|
|
delay(1000);
|
|
if (preview) {
|
|
do {
|
|
delay(10);
|
|
} while (isPlaying());
|
|
if (previewFromFolder == 0)
|
|
mp3.playFolderTrack(returnValue, 1);
|
|
else
|
|
mp3.playFolderTrack(previewFromFolder, returnValue);
|
|
}
|
|
} else
|
|
ignoreDownButton = false;
|
|
}
|
|
} while (true);
|
|
}
|
|
|
|
void resetCard() {
|
|
do {
|
|
pauseButton.read();
|
|
upButton.read();
|
|
downButton.read();
|
|
|
|
if (upButton.wasReleased() || downButton.wasReleased()) {
|
|
Serial.print(F("Abgebrochen!"));
|
|
mp3.playMp3FolderTrack(802);
|
|
return;
|
|
}
|
|
} while (!mfrc522.PICC_IsNewCardPresent());
|
|
|
|
if (!mfrc522.PICC_ReadCardSerial())
|
|
return;
|
|
|
|
Serial.print(F("Karte wird neu Konfiguriert!"));
|
|
setupCard();
|
|
}
|
|
|
|
void setupCard() {
|
|
mp3.pause();
|
|
Serial.print(F("Neue Karte konfigurieren"));
|
|
|
|
// Ordner abfragen
|
|
myCard.folder = voiceMenu(99, 300, 0, true);
|
|
|
|
// Wiedergabemodus abfragen
|
|
myCard.mode = voiceMenu(6, 310, 310);
|
|
|
|
// Hörbuchmodus -> Fortschritt im EEPROM auf 1 setzen
|
|
EEPROM.write(myCard.folder,1);
|
|
|
|
// Einzelmodus -> Datei abfragen
|
|
if (myCard.mode == 4)
|
|
myCard.special = voiceMenu(mp3.getFolderTrackCount(myCard.folder), 320, 0,
|
|
true, myCard.folder);
|
|
|
|
// Admin Funktionen
|
|
if (myCard.mode == 6)
|
|
myCard.special = voiceMenu(3, 320, 320);
|
|
|
|
// Karte ist konfiguriert -> speichern
|
|
mp3.pause();
|
|
writeCard(myCard);
|
|
}
|
|
|
|
bool readCard(nfcTagObject *nfcTag) {
|
|
bool returnValue = true;
|
|
// Show some details of the PICC (that is: the tag/card)
|
|
Serial.print(F("Card UID:"));
|
|
dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
|
|
Serial.println();
|
|
Serial.print(F("PICC type: "));
|
|
MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
|
|
Serial.println(mfrc522.PICC_GetTypeName(piccType));
|
|
|
|
byte buffer[18];
|
|
byte size = sizeof(buffer);
|
|
|
|
// Authenticate using key A
|
|
Serial.println(F("Authenticating using key A..."));
|
|
status = (MFRC522::StatusCode)mfrc522.PCD_Authenticate(
|
|
MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
|
|
if (status != MFRC522::STATUS_OK) {
|
|
returnValue = false;
|
|
Serial.print(F("PCD_Authenticate() failed: "));
|
|
Serial.println(mfrc522.GetStatusCodeName(status));
|
|
return;
|
|
}
|
|
|
|
// Show the whole sector as it currently is
|
|
Serial.println(F("Current data in sector:"));
|
|
mfrc522.PICC_DumpMifareClassicSectorToSerial(&(mfrc522.uid), &key, sector);
|
|
Serial.println();
|
|
|
|
// Read data from the block
|
|
Serial.print(F("Reading data from block "));
|
|
Serial.print(blockAddr);
|
|
Serial.println(F(" ..."));
|
|
status = (MFRC522::StatusCode)mfrc522.MIFARE_Read(blockAddr, buffer, &size);
|
|
if (status != MFRC522::STATUS_OK) {
|
|
returnValue = false;
|
|
Serial.print(F("MIFARE_Read() failed: "));
|
|
Serial.println(mfrc522.GetStatusCodeName(status));
|
|
}
|
|
Serial.print(F("Data in block "));
|
|
Serial.print(blockAddr);
|
|
Serial.println(F(":"));
|
|
dump_byte_array(buffer, 16);
|
|
Serial.println();
|
|
Serial.println();
|
|
|
|
uint32_t tempCookie;
|
|
tempCookie = (uint32_t)buffer[0] << 24;
|
|
tempCookie += (uint32_t)buffer[1] << 16;
|
|
tempCookie += (uint32_t)buffer[2] << 8;
|
|
tempCookie += (uint32_t)buffer[3];
|
|
|
|
nfcTag->cookie = tempCookie;
|
|
nfcTag->version = buffer[4];
|
|
nfcTag->folder = buffer[5];
|
|
nfcTag->mode = buffer[6];
|
|
nfcTag->special = buffer[7];
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
void writeCard(nfcTagObject nfcTag) {
|
|
MFRC522::PICC_Type mifareType;
|
|
byte buffer[16] = {0x13, 0x37, 0xb3, 0x47, // 0x1337 0xb347 magic cookie to
|
|
// identify our nfc tags
|
|
0x01, // version 1
|
|
nfcTag.folder, // the folder picked by the user
|
|
nfcTag.mode, // the playback mode picked by the user
|
|
nfcTag.special, // track or function for admin cards
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
|
|
byte size = sizeof(buffer);
|
|
|
|
mifareType = mfrc522.PICC_GetType(mfrc522.uid.sak);
|
|
|
|
// Authenticate using key B
|
|
Serial.println(F("Authenticating again using key B..."));
|
|
status = (MFRC522::StatusCode)mfrc522.PCD_Authenticate(
|
|
MFRC522::PICC_CMD_MF_AUTH_KEY_B, trailerBlock, &key, &(mfrc522.uid));
|
|
if (status != MFRC522::STATUS_OK) {
|
|
Serial.print(F("PCD_Authenticate() failed: "));
|
|
Serial.println(mfrc522.GetStatusCodeName(status));
|
|
mp3.playMp3FolderTrack(401);
|
|
return;
|
|
}
|
|
|
|
// Write data to the block
|
|
Serial.print(F("Writing data into block "));
|
|
Serial.print(blockAddr);
|
|
Serial.println(F(" ..."));
|
|
dump_byte_array(buffer, 16);
|
|
Serial.println();
|
|
status = (MFRC522::StatusCode)mfrc522.MIFARE_Write(blockAddr, buffer, 16);
|
|
if (status != MFRC522::STATUS_OK) {
|
|
Serial.print(F("MIFARE_Write() failed: "));
|
|
Serial.println(mfrc522.GetStatusCodeName(status));
|
|
mp3.playMp3FolderTrack(401);
|
|
}
|
|
else
|
|
mp3.playMp3FolderTrack(400);
|
|
Serial.println();
|
|
delay(100);
|
|
}
|
|
|
|
/**
|
|
* Helper routine to dump a byte array as hex values to Serial.
|
|
*/
|
|
void dump_byte_array(byte *buffer, byte bufferSize) {
|
|
for (byte i = 0; i < bufferSize; i++) {
|
|
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
|
|
Serial.print(buffer[i], HEX);
|
|
}
|
|
}
|