48 Sounds aus einem IO

KOLocoNetSound Prototyp 1000pxDieses MP3-Soundmodul ist eine Weiterentwicklung des KO-LocoNetIO. 
Die KO-UniLoconet-Platine dient, wie für alle dieser Entwicklungen als Hardwarebasis. Die Erweiterung besteht im wesentlichen aus einer kleinen Platine, die auf den IO-Steckplatz aufgesteckt wird. Sie trägt einen kleinen DFPlayer, ein Mini-MP3-Player. Dieser hat einen Slot für MiniSDCard und eine kleine Stereoendstufe. Also alles was ein richtiges Soundpaket ausmacht. Alle Parameter werden über eine serielle Schnittstelle an den Winzling übertragen. Wem die Power nicht ausreicht, kann mit entsprechenden Verstärkern nachhelfen.

Allerdings werden für diesen Baustein gleich 42 fortlaufende Adressen benötigt. Zur Einrichtung werden 16 Basisadressen belegt. Hinter jeder dieser Adressen werden intern nochmals +16 und +32 angesprochen. Siehe Dialog Programmieren->LocNnet->LocoIO. Die Lautstärke wird in RocRail im Reiter "OPC" Opcode, Argument1, Argument2 mit Werten zwischen 0 - 127 festgelegt.

Daraus kann ein tolles Klangerlebnis an lokalen Schwerpunkten erzeugt werden.
In einer künftigen Weiterentwicklung sollen die Sonds der unteren 16 Adressen als sogenannter Soundteppich eingespielt werden.

LocoNetSound unter Racrail einrichten:

Menu

Voraussetzung ist, dass eine funktionierende Verbindung über eine LocoNetkompatible Zentrale besteht.

Über den Menüpunkt Programmieren -> LocoNet -> LocoIO wird der eigentliche Programmierdialog geöffnet.
Dieser mehrseitige Dialog ist sehr mächtig. Seine sieben Reiter gruppieren zusammengehörige Konfigurationsgruppen.

Beim Aufruf des Dialoges befindet man sich in der Konfigurationsgruppe "Adresses". 
Nach einem Mausklick auf Query werden alle LocoNet-Module, welche an diesem Bus hängen und die Abfrage erkennen, mit ihrer Moduladresse und Firmwareversion aufgelistet.
Die Versionsnummern lassen auf die Verwendung des Moduls schließen.

IO AdressAktuell gilt: 

V-Nr.: Verwendung  Info
101 Servo  
102 EBF-Servo Arduino
149 IO  
150 EBF-IO Arduino
151 EBF-IO Arduino
841 EBF-MP3-Sound Arduino


Nachdem das IO-Modul markiert ist kann im Konfigurationsbereich "Easy Setup" mit Get All die Konfiguration der Ports ausgelesen werden. 

IO EasySetupIn der Titelzeile des Dialogs wird die aktuelle Moduladresse angezeigt.
Jeder der 16 zur Verfügung stehenden Ports kann auf dieser Dialogseite konfiguriert werden.
Zunächst sollte jeder Port seine eigene Adresse besitzen, sonst kann es zu unerwünschten Adresskonflikten führen.
Im nächsten Schritt wird jedem Port seine Aufgabe zugewiesen:

Sind die Einstellungen alle vorgenommen wird mit Set All die neue Kofiguration in das EPROM des Moduls geschrieben. Zusätzlich sollte die Konfiguration nochmals lokal mit Save gespeichert werden. 

MP3 OPC

In der Kofigurationsgruppe "OPC" werden nun für die Ports  die einzelnen Soundpegel vergeben. Port auswählen und mit Get die aktuellen Soundpegel aus dem EPROM des Moduls lesen.

Im Beispiel hat der Port 16 die Adresse 100. So ergeben sich folgen Zuweisungen:

Adresse 100: Wert: 0 - 127 in Opcode = Soundpegel von 000_AbfahrtVonGleis1.mp3
Adresse 116: Wert: 0 - 127 in Argument1 = Soundpegel von 016_Windrauschen.mp3
Adresse 132: Wert: 0 - 127 in Argument2 = Soundpegel von 032_Bach.mp3
Nach einem klick auf Set werden die Daten ins EProm des Soundmoduls geschrieben. 

Damit das so einfach funktioniert dürfen die MP3-Dateien nicht mit Klartextnamen, wie "AbfahrtVonGleis1.mp3" versehen werden, sondern müssen fortlaufend als "000_AbfahrtVonGleis1.mp3", "001_AbfahrtVonGleis2.mp3" ... "047_Kuhweide.mp3" auf der Speicherkarte gespeichert werden.Somit bleibt die richtige Zuweisung von Adresse und Sounddatei gewährleistet.

Nach dem Programmieren sollten die Module kurz ausgeschaltet werden, damit sie komplett neu hochfahren. Danach sind sie einsatzbereit.

Quelle: https://wiki.rocrail.net/doku.php?id=lnsv-de

 

 Arduino-Sketch

/**************************************************************************
KOLocoMP3: Karlheinz Oestreicher www.eisenbahnfreunde99.de
Software besed on LocoIno
----------------------------------------------------------------------------
Config:
DFPlayer - A Mini MP3 Player For Arduino <https://www.dfrobot.com/product-1121.html>
Shield: EBF KO UniLocoNet
K10 Pin GND -> DFPlayer GND
K10 Pin 7 -> DFPlayer TX
K10 Pin 8 -> 1KOhm -> DFPlayer RX
K10 Pin +5V -> DFPlayer VCC
Adressen:
Port 1-16 Adressen frei vergebbar wie IO Volume: OPC-Opcode 0-30 Ambiente fortlaufende Hintergrundgeräusche
Port 17-32 Adressen wie Port 1-16 + 16 Volume: OPC-Argument1 0-30
Port 33-48 Adressen wie Port 1-16 + 32 Volume: OPC-Argument2 0-30

------------------------------------------------------------------------------
LocoIno - Configurable Arduino Loconet Module
Copyright (C) 2014 Daniel Guisado Serra
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
------------------------------------------------------------------------
AUTHOR : Dani Guisado - http://www.clubncaldes.com
------------------------------------------------------------------------
DESCRIPTION:
This software emulates the functionality of a GCA50 board from Peter
Giling (Giling Computer Applications). This is a Loconet Interface
with 16 I/O that can be individually configured as Input (block sensors)
or Outputs (switches, lights,...).
Configuration is done through SV Loconet protocol and can be configured
from Rocrail (Programming->GCA->GCA50).
------------------------------------------------------------------------
PIN ASSIGNMENT:
0,1 -> Serial, used to debug and Loconet Monitor (uncomment DEBUG)
2,3,4,5,6 -> Configurable I/O from 1 to 5
7 -> Loconet TX (connected to GCA185 shield)
8 -> Loconet RX (connected to GCA185 shield)
9,10,11,12,13 -> Configurable I/O from 6 to 10
A0,A1,A2,A3,A4,A5-> Configurable I/O from 11 to 16
------------------------------------------------------------------------
CREDITS:
* Based on MRRwA Loconet libraries for Arduino - http://mrrwa.org/ and
the Loconet Monitor example.
* Inspired in GCA50 board from Peter Giling - http://www.phgiling.net/
* Idea also inspired in LocoShield from SPCoast - http://www.scuba.net/
* Thanks also to Rocrail group - http://www.rocrail.net
*************************************************************************/

#include
#include
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

//Uncomment this line to debug through the serial monitor
//#define DEBUG
#define VERSION 481

namespace {
//#define VIDA_LOCOSHIELD_NANO 1

//Arduino pin assignment to each of the 16 outputs
#ifdef VIDA_LOCOSHIELD_NANO
uint8_t pinMap[16]={11,10,9,6,5,4,3,2,15,14,19,18,17,16,13,12};
#else
uint8_t pinMap[16]={2,3,4,5,6,9,10,11,12,13,14,15,16,17,18,19};
#endif
}

 

//Timers for each input in case of using "block" configuration instead of "input" configuration
//input defined as "block" will keep the signal high at least 2 seconds
unsigned long inpTimer[16];
static LnBuf LnTxBuffer ;

//3 bytes defining a pin behavior ( http://wiki.rocrail.net/doku.php?id=loconet-io-en )
typedef struct
{
uint8_t cnfg;
uint8_t value1;
uint8_t value2;
} PIN_CFG;

//Memory map exchanged with SV read and write commands ( http://wiki.rocrail.net/doku.php?id=lnsv-en )
typedef struct
{
uint8_t vrsion;
uint8_t addr_low;
uint8_t addr_high;
PIN_CFG pincfg[16];
} SV_TABLE;

//Union to access the data with the struct or by index
typedef union {
SV_TABLE svt;
uint8_t data[101];
} SV_DATA;

SV_DATA svtable;
lnMsg *LnPacket;


void setup()
{
int n,m;
uint16_t myAddr;

mySoftwareSerial.begin(9600);
myDFPlayer.begin(mySoftwareSerial);
myDFPlayer.EQ(DFPLAYER_EQ_BASS);
myDFPlayer.volume(10); //Set volume value. From 0 to 30
myDFPlayer.play(1); //Play the first mp3

// First initialize the LocoNet interface
LocoNet.init(7);

// Configure the serial port for 57600 baud
//#ifdef DEBUG
Serial.begin(9600);
Serial.print("KOLocoMP3 v.");Serial.println(VERSION);
//#endif

//Load config from EEPROM
for (n=0;n<101;n++)
svtable.data[n]=EEPROM.read(n);

if (svtable.svt.vrsion!=VERSION || svtable.svt.addr_low<1 || svtable.svt.addr_low>240 || svtable.svt.addr_high<1 || svtable.svt.addr_high>100 )
{
svtable.svt.vrsion=VERSION;
svtable.svt.addr_low=81;
svtable.svt.addr_high=1;
EEPROM.write(0,VERSION);
EEPROM.write(1, svtable.svt.addr_low);
EEPROM.write(2, svtable.svt.addr_high);
}
else
{
//Configure I/O
#ifdef DEBUG
Serial.println("Initializing pins...");
#endif
for (n=0;n<16;n++)
{
inpTimer[n]=0; //timer initialization
myAddr=(svtable.svt.pincfg[n].value2 & B00001111)<<7;
myAddr=myAddr|svtable.svt.pincfg[n].value1;
if (bitRead(svtable.svt.pincfg[n].cnfg,7))
{
#ifdef DEBUG
Serial.print("Pin ");Serial.print(pinMap[n]); Serial.print(" output "); Serial.print(n); Serial.print(" LOGIC "); Serial.print(myAddr); Serial.println(" as OUTPUT");
#endif
//pinMode(pinMap[n],OUTPUT); //
//IF HIGH at startup AND output type = CONTINUE ...
if (bitRead(svtable.svt.pincfg[n].cnfg,0)==0 && bitRead(svtable.svt.pincfg[n].cnfg,3)==0)
//digitalWrite(pinMap[n],HIGH);
delay(0);
else
//digitalWrite(pinMap[n],LOW);
delay(0);
}
else
{
#ifdef DEBUG
Serial.print("Pin ");Serial.print(pinMap[n]); Serial.print(" output "); Serial.print(n); Serial.print(" LOGIC "); Serial.print(myAddr); Serial.println(" as INPUT_PULLUP");
#endif
pinMode(pinMap[n],INPUT_PULLUP);
//bitWrite(svtable.svt.pincfg[n].value2,4,digitalRead(pinMap[n]));
}
}
}

Serial.print("Module ");Serial.print(svtable.svt.addr_low);Serial.print("/");Serial.println(svtable.svt.addr_high);
//IOinit();
}

void sendSensor(int Adr, boolean state) {
//Adressen von 1 bis 4096 akzeptieren
if (Adr <= 0) //nur korrekte Adressen
return;
Adr = Adr - 1;
int D2 = Adr >> 1; //Adresse Teil 1 erstellen
bitWrite(D2,7,0); //A1 bis A7

int D3 = Adr >> 8; //Adresse Teil 2 erstellen, A8 bis A11
bitWrite(D3,4, state); //Zustand ausgeben
bitWrite(D3,5,bitRead(Adr,0)); //Adr Bit0 = A0

//Checksum bestimmen:
int D4 = 0xFF; //Invertierung setzten
D4 = OPC_INPUT_REP ^ D2 ^ D3 ^ D4; //XOR
bitWrite(D4,7,0); //MSB Null setzten

addByteLnBuf( &LnTxBuffer, OPC_INPUT_REP ) ; //0xB2
addByteLnBuf( &LnTxBuffer, D2 ) ; //1. Daten Byte IN2
addByteLnBuf( &LnTxBuffer, D3 ) ; //2. Daten Byte IN2
addByteLnBuf( &LnTxBuffer, D4 ) ; //Prüfsumme
addByteLnBuf( &LnTxBuffer, 0xFF ) ; //Trennbit

// Check to see if we have received a complete packet yet
LnPacket = recvLnMsg( &LnTxBuffer ); //Senden vorbereiten
if(LnPacket ) { //korrektheit Prüfen
LocoNet.send( LnPacket ); // Send the received packet from the PC to the LocoNet
}
}

void IOinit()
{
int currentState;
int n;
bool hasChanged;
Serial.println(" IOinit ");
for (n=0; n<16; n++)
{
if (!bitRead(svtable.svt.pincfg[n].cnfg,7)) //Setup as an Input
{
currentState=digitalRead(pinMap[n]);
if(currentState == LOW){
Serial.print(" Pin: "); Serial.print(svtable.svt.pincfg[n].value1);
//bitWrite(svtable.svt.pincfg[n].value2,4,currentState);
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[n].value1, svtable.svt.pincfg[n].value2);
bitWrite(svtable.svt.pincfg[n].value2,4,currentState);
}
}
}
return;
}

void loop()
{
int n;
bool hasChanged;
int currentState;

// Check for any received LocoNet packets
LnPacket = LocoNet.receive() ;
if( LnPacket )
{
Serial.println("e"); // LocoNet empfangen
#ifdef DEBUG
// First print out the packet in HEX
Serial.print("RX: ");
uint8_t msgLen = getLnMsgSize(LnPacket);
for (uint8_t x = 0; x < msgLen; x++)
{
uint8_t val = LnPacket->data[x];
// Print a leading 0 if less than 16 to make 2 HEX digits
if(val < 16)
Serial.print('0');
Serial.print(val, HEX);
Serial.print(' ');
}
Serial.println();
#endif

// If this packet was not a Switch or Sensor Message checks por PEER packet
if(!LocoNet.processSwitchSensorMessage(LnPacket))
{
processPeerPacket();
}
}

// Check inputs to inform
for (n=0; n<16; n++)
{
if (!bitRead(svtable.svt.pincfg[n].cnfg,7)) //Setup as an Input
{
//Check if state changed
currentState=digitalRead(pinMap[n]);
if (currentState==bitRead(svtable.svt.pincfg[n].value2,4))
{
inpTimer[n]=millis();
continue;
}

hasChanged=true;
//check if is a BLOCK DETECTOR with DELAYED SWITCH OFF (as we use pullup resistor, deactivation is HIGH)
if (bitRead(svtable.svt.pincfg[n].cnfg,4)==1 && bitRead(svtable.svt.pincfg[n].cnfg,2)==0 && currentState==HIGH)
{
if ((millis()-inpTimer[n])<2000)
hasChanged=false;
}

if (hasChanged)
{
#ifdef DEBUG
Serial.println("INPUT changed ");
Serial.print("INPUT ");Serial.print(n);
Serial.print(" IN PIN "); Serial.print(pinMap[n]);
Serial.print(" svtable.svt.pincfg[n] "); //Serial.print(svtable.svt.pincfg[n]);
Serial.print(" value1 "); Serial.print(svtable.svt.pincfg[n].value1);
Serial.print(" value2 "); Serial.print(svtable.svt.pincfg[n].value2);
Serial.print(" CHANGED, INFORM "); Serial.println((svtable.svt.pincfg[n].value1<<1 | bitRead(svtable.svt.pincfg[n].value2,5))+1);
#endif
//LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[n].value1, svtable.svt.pincfg[n].value2);
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[n].value1, svtable.svt.pincfg[n].value2);
//Update state to detect flank (use bit in value2 of SV)
bitWrite(svtable.svt.pincfg[n].value2,4,currentState);
Serial.println("s"); // LocoNet senden
}

}
}

}

// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Sensor messages
void notifySensor( uint16_t Address, uint8_t State )
{
//IOinit_();
#ifdef DEBUG
Serial.print("notifySensor: ");
Serial.print("Sensor: ");
Serial.print(Address, DEC);
Serial.print(" - ");
Serial.println( State ? "Active" : "Inactive" );
#endif
}

// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch Request messages
void notifySwitchRequest( uint16_t Address, uint8_t Output, uint8_t Direction )
{
int n, n2;
uint16_t opc, opc1, opc2, opc3;
uint16_t myAddr, tmpAddr;
boolean addr = false;

//Direction must be changed to 0 or 1, not 0 or 32
Direction ? Direction=1 : Direction=0;

#ifdef DEBUG
Serial.print("Switch Request: ");
Serial.print(Address, DEC);
Serial.print(':');
Serial.print(Direction ? "Closed" : "Thrown");
Serial.print(" - ");
Serial.println(Output ? "On" : "Off");
#endif

//Check if the Address is assigned, configured as output and same Direction
for (n=0; n<16; n++)
{
myAddr=(svtable.svt.pincfg[n].value2 & B00001111)<<7;
myAddr=myAddr|svtable.svt.pincfg[n].value1;
if (myAddr == Address-1){
opc = svtable.data[(n+1)*3+48];
n2 = n;
addr = true;
}
if (myAddr + 16 == Address-1){
opc = svtable.data[(n+1)*3+49];
n2 = n+16;
addr = true;
}
if (myAddr + 32 == Address-1){
opc = svtable.data[(n+1)*3+50];
n2 = n+32;
addr = true;
}
//if ((myAddr == Address-1) && //Address
if ((addr == true) && //Address
(bitRead(svtable.svt.pincfg[n].cnfg,7) == 1)) //Setup as an Output
{
#ifdef DEBUG
Serial.print("Output port ");
Serial.print(n);
Serial.print(" Adresse: "); Serial.print(Address); Serial.print(" Sound-Nr.: "); Serial.println(n);
Serial.print(" OPC1: "); Serial.print(opc); Serial.print(" Volume: "); Serial.println(n2);
#endif
//If pulse (always hardware reset) and Direction, only listen ON message
if (bitRead(svtable.svt.pincfg[n].cnfg,3) == 1 && bitRead(svtable.svt.pincfg[n].value2,5) == Direction && Output)
{
digitalWrite(pinMap[n], HIGH);
delay(150);
digitalWrite(pinMap[n], LOW);
break;
}
//If continue and hardware reset and Direction
else if (bitRead(svtable.svt.pincfg[n].cnfg,3)==0 && bitRead(svtable.svt.pincfg[n].cnfg,2)==1 && bitRead(svtable.svt.pincfg[n].value2,5) == Direction)
{
if (Output)
digitalWrite(pinMap[n], HIGH);
else
digitalWrite(pinMap[n], LOW);
break;
}
//If continue and software reset, one Direction ON turns on and other Direction ON turns off
//OFF messages are not listened
else if (bitRead(svtable.svt.pincfg[n].cnfg,3)==0 && bitRead(svtable.svt.pincfg[n].cnfg,2)==0 && Output)
{
if (!Direction){
digitalWrite(pinMap[n], HIGH);
//mp3_play(n2, opc);
}
else{
digitalWrite(pinMap[n], LOW);
mp3_play(n2+1, opc);
}
break;
}
#ifdef DEBUG

#endif
}

}
}

void mp3_play(int filenr, int volume){
Serial.print("MP3-Player FileNr.: "); Serial.print(filenr); Serial.print(" Volume: "); Serial.println(volume);
myDFPlayer.volume(volume); //Set volume value. From 0 to 30
myDFPlayer.play(filenr); //Play the first mp3
}
// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch Report messages
void notifySwitchReport( uint16_t Address, uint8_t Output, uint8_t Direction )
{
#ifdef DEBUG
Serial.print("Switch Report: ");
Serial.print(Address, DEC);
Serial.print(':');
Serial.print(Direction ? "Closed" : "Thrown");
Serial.print(" - ");
Serial.println(Output ? "On" : "Off");
#endif
}

// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Switch State messages
void notifySwitchState( uint16_t Address, uint8_t Output, uint8_t Direction )
{
//IOinit_();
#ifdef DEBUG
Serial.print("Switch State: ");
Serial.print(Address, DEC);
Serial.print(':');
Serial.print(Direction ? "Closed" : "Thrown");
Serial.print(" - ");
Serial.println(Output ? "On" : "Off");
#endif
}

boolean processPeerPacket()
{
//Check is a OPC_PEER_XFER message
if (LnPacket->px.command != OPC_PEER_XFER) return(false);

//Check is my destination
if ((LnPacket->px.dst_l!=0 || LnPacket->px.d5!=0) &&
(LnPacket->px.dst_l!=0x7f || LnPacket->px.d5!=svtable.svt.addr_high) &&
(LnPacket->px.dst_l!=svtable.svt.addr_low || LnPacket->px.d5!=svtable.svt.addr_high))
{
#ifdef DEBUG
Serial.println("OPC_PEER_XFER not for me!");
Serial.print("LnPacket->px.dst_l: ");Serial.print(LnPacket->px.dst_l);Serial.print(" Addr low: ");Serial.println(svtable.svt.addr_low);
Serial.print("LnPacket->px.d5: ");Serial.print(LnPacket->px.d5);Serial.print(" Addr high: ");Serial.println(svtable.svt.addr_high);
Serial.print("LnPacket->px.dst_h: ");Serial.print(LnPacket->px.dst_h);Serial.print(" Addr high: ");Serial.println(svtable.svt.addr_high);
Serial.print("LnPacket->px.d1: ");Serial.println(LnPacket->px.d1);
Serial.print("LnPacket->px.d2: ");Serial.println(LnPacket->px.d2);
#endif
return(false);
}

//Set high bits in right position
bitWrite(LnPacket->px.d1,7,bitRead(LnPacket->px.pxct1,0));
bitWrite(LnPacket->px.d2,7,bitRead(LnPacket->px.pxct1,1));
bitWrite(LnPacket->px.d3,7,bitRead(LnPacket->px.pxct1,2));
bitWrite(LnPacket->px.d4,7,bitRead(LnPacket->px.pxct1,3));

bitWrite(LnPacket->px.d5,7,bitRead(LnPacket->px.pxct2,0));
bitWrite(LnPacket->px.d6,7,bitRead(LnPacket->px.pxct2,1));
bitWrite(LnPacket->px.d7,7,bitRead(LnPacket->px.pxct2,2));
bitWrite(LnPacket->px.d8,7,bitRead(LnPacket->px.pxct2,3));
#ifdef DEBUG
Serial.println();
//Serial.print("LnPack ");Serial.print(LnPacket->px.d2);Serial.print(" ");Serial.print(LnPacket->px.d2+1);Serial.print(" ");Serial.println(LnPacket->px.d2+2);
Serial.print("px.d1: ");Serial.println(LnPacket->px.d1);
Serial.print("px.d2: ");Serial.println(LnPacket->px.d2);
Serial.print("px.d3: ");Serial.println(LnPacket->px.d3);
Serial.print("px.d4: ");Serial.println(LnPacket->px.d4);
Serial.print("px.d5: ");Serial.println(LnPacket->px.d5);
Serial.print("px.d6: ");Serial.println(LnPacket->px.d6);
Serial.print("px.d7: ");Serial.println(LnPacket->px.d7);
Serial.print("px.d8: ");Serial.println(LnPacket->px.d8);
#endif

//OPC_PEER_XFER D1 -> Command (1 SV write, 2 SV read)
//OPC_PEER_XFER D2 -> Register to read or write
if (LnPacket->px.d1==2)
{
#ifdef DEBUG
Serial.print("READ ");Serial.print(LnPacket->px.d2);Serial.print(" ");Serial.print(LnPacket->px.d2+1);Serial.print(" ");Serial.println(LnPacket->px.d2+2);
#endif
sendPeerPacket(svtable.data[LnPacket->px.d2], svtable.data[LnPacket->px.d2+1], svtable.data[LnPacket->px.d2+2]);
return (true);
}

//Write command
if (LnPacket->px.d1==1)
{
//SV 0 contains the program version (write SV0 == RESET? )
if (LnPacket->px.d2>0)
{
//Store data
Serial.println("im EEProm speichern");
if(LnPacket->px.d2 > 50){ // Lautstärke des MP3-Moduls
if(LnPacket->px.d4 > 30) LnPacket->px.d4 = 15; // Max = 30; Lautstärke auf mittleren Pegel einstellen, wenn Max überschritten wird
}
svtable.data[LnPacket->px.d2]=LnPacket->px.d4;
EEPROM.write(LnPacket->px.d2,LnPacket->px.d4);

#ifdef DEBUG
Serial.print("EEProm write ESCRITURA "); Serial.print(LnPacket->px.d2); Serial.print(" <== ");
Serial.print(LnPacket->px.d4); Serial.print(" | ");
Serial.print(LnPacket->px.d4, HEX); Serial.print(" | ");
Serial.println(LnPacket->px.d4, BIN);
#endif
}

//Answer packet
sendPeerPacket(0x00, 0x00, LnPacket->px.d4);
#ifdef DEBUG
Serial.println(">> OPC_PEER_XFER answer sent");
#endif
return (true);
}

return (false);

}

void sendPeerPacket(uint8_t p0, uint8_t p1, uint8_t p2)
{
lnMsg txPacket;

txPacket.px.command=OPC_PEER_XFER;
txPacket.px.mesg_size=0x10;
txPacket.px.src=svtable.svt.addr_low;
txPacket.px.dst_l=LnPacket->px.src;
txPacket.px.dst_h=LnPacket->px.dst_h;
txPacket.px.pxct1=0x00;
txPacket.px.d1=LnPacket->px.d1; //Original command
txPacket.px.d2=LnPacket->px.d2; //SV requested
txPacket.px.d3=svtable.svt.vrsion;
txPacket.px.d4=0x00;
txPacket.px.pxct2=0x00;
txPacket.px.d5=svtable.svt.addr_high; //SOURCE high address
txPacket.px.d6=p0;
txPacket.px.d7=p1;
txPacket.px.d8=p2;

//Set high bits in right position
bitWrite(txPacket.px.pxct1,0,bitRead(txPacket.px.d1,7));
bitClear(txPacket.px.d1,7);
bitWrite(txPacket.px.pxct1,1,bitRead(txPacket.px.d2,7));
bitClear(txPacket.px.d2,7);
bitWrite(txPacket.px.pxct1,2,bitRead(txPacket.px.d3,7));
bitClear(txPacket.px.d3,7);
bitWrite(txPacket.px.pxct1,3,bitRead(txPacket.px.d4,7));
bitClear(txPacket.px.d4,7);
bitWrite(txPacket.px.pxct2,0,bitRead(txPacket.px.d5,7));
bitClear(txPacket.px.d5,7);
bitWrite(txPacket.px.pxct2,1,bitRead(txPacket.px.d6,7));
bitClear(txPacket.px.d6,7);
bitWrite(txPacket.px.pxct2,2,bitRead(txPacket.px.d7,7));
bitClear(txPacket.px.d7,7);
bitWrite(txPacket.px.pxct2,3,bitRead(txPacket.px.d8,7));
bitClear(txPacket.px.d8,7);

LocoNet.send(&txPacket);

#ifdef DEBUG
Serial.println("OPC_PEER_XFER Packet sent!");
#endif
}