Bis Ende 2018 setzten wir auf unserer Modulanlage zu größten Teil Nachbauten von HDL ein. Dies galt sowohl für den IO Vers. 149 und des Servotreibers Vers. 101. Leider hatten wir vor allem beim IO immer wieder Probleme mit der Zuverläsigkeit. Diese hätten durch neuere Firmwareversionen seitens von HDL abgestellt werden können. Leider gab es diverse rechtliche Konflikte mit Digitrax, als ursprüglicher LocoNet-Entwickler, weswegen die Frimware nicht mehr frei verfügbar war. Ein Upgrade hätte unseren Verein je Baustein ca. 7€ gekostet. Bei schätzungsweise 40 eingesetzten IOs hätten wir rund 280€ in neue PICs investieren müssen.

Unsere Elektroniker hielten deshalb schon längere Zeit nach Alternativen Ausschau. Und siehe da, die Arduino-Szene zeigte durchaus professionelle Lösungsansätze. Karlheinz Oestreicher hat sich vor allem von den Ausführungen von Philipp Gathow inspireren lassen. http://pgahtow.de/wiki/index.php?title=Loconet 
Weitere grundlegende Harware- und Softwarequellen sind unter diesem Link zu funden: https://mrrwa.org/loconet-interface/
Aus diesen Informationen entstand unsere Einheitsplatine "KO UniLocoNet". Sie bildet die Hardware zur LocoNet-Anbindung. Je nach Frimware kann dann daraus ein IO-, Servo oder MP3-Sound-Baustein erstellet werden. Der Vorteil dieser Hardwarevariante: die Firmware gibt das Szenario vor - die Hardware bleibt! 

Schaltung IO Servo

PlatinenLayoutV1

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
}

16 Ein- / Ausgänge für vielfältige Funktionen

Die Firmware gründet auf der Entwicklung von Dani Guisado - http://www.clubncaldes.com.

Innerhalb Rocrail bildet sie die Funktionalität des LocoNet-Bausteiens CGA50 ab. 

In unserem Funktionsumfang haben wir eine Statusaktualisierung nach jeder Gleisspannungsfreischaltung implementiert und sie mit der Vers-Nr.: 151 versehen.
Auf unserer Modulanlage erfüllt der Baustein alle Aufgaben aus den Bereichen melden, schalten und signalisieren. Durch seine universell konfigurierbaren Ports bleiben fast keine Wünsche mehr offen.

IO 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:
INPUT: Der Eingang reagiert sobald sein Pegel auf Low liegt. Es gibt keine Ein-/Ausschaltverzögerung. D.h. jedes prellen eines Tasters erzeugt Datenverkehr auf dem LocoNet-Bus. Daher sollte diese Funktionsweise nicht für unentprellte Rückmelder genutzt werden.
BLOCK: Auch hier reagiert der Eingang sobald sein Pegel auf Low liegt. Egal wieviele Trigger folgen bleibt sein Zustand stabil. Erst nach ca. 3-4 Sekunden nach dem letzten Trigger geht es zurück in den Ausgangszustand. Diese Betriebsart setzen wir zum Melden auf der Strecke ein. Sie ist selbst bei kurzen Punktmeldern zuverlässig.
SWITCH: In dieser Betriebsart ist der Port als Ausgang definiert. Mit der Option C2 (2 Coils = 2 Spulen) können zb.Weichen oder Signale mit Spulenantrieben angesteuert werden. Hierzu wird noch ein weiterer Port als Ausgang mit der gleichen Adresse definiert und einer der beiden erhält die Option C2. Die Ausgänge schalten nun im Wechsel von High nach Low.
PULSE: Diese Betriebsart entspricht nachezu der von Switch. Allerdings wird der Ausgang nach kurzer Zeit wieder zurück gesetzt.
In der Feldern Usage steht die verwendete RocRailkennung des Ports.

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. 

Nach dem Programmieren sollten die Module kurz ausgeschaltet werden, damit sie komplett neu hochfahren. Danach sind sie einsatzbereit.
ACHTUNG: Zum treiben von größeren Lasten müssen Schaltverstärker nachgeschaltet werden.

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

 Arduino-Sketch

 /**************************************************************************
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

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

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[51];
} SV_DATA;

SV_DATA svtable;
lnMsg *LnPacket;


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

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

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

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

// KO print CV
#ifdef DEBUG
m=0;
for (n=0;n<51;n++){
Serial.print(" "); Serial.print(n); Serial.print(": "); Serial.print(svtable.data[n], DEC);
m++;
if(m>6){m=0; Serial.println();}
}
Serial.println();
for (n=0;n<16;n++){
Serial.print(n); Serial.print(" value1: "); Serial.print(svtable.svt.pincfg[n].value1, DEC); Serial.print(" value2: "); Serial.print(svtable.svt.pincfg[n].value2, DEC); Serial.print(" cnfg: "); Serial.print(svtable.svt.pincfg[n].cnfg, DEC);
Serial.println();
}
#endif

//Check for a valid config
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);
else
digitalWrite(pinMap[n],LOW);
}
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) { //Routine aus LocoNetFeedback von P. Gathow übernommen
//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_() // liest alle Inputs aus und schickt sie beim Power ON an die Zentrale
{
int n;
int Adr;
//Serial.println(" IOinit_ ");
for (n=0; n<16; n++)
{
if (!bitRead(svtable.svt.pincfg[n].cnfg,7)) //Setup as an Input
{
Adr = (svtable.svt.pincfg[n].value1<<1 | bitRead(svtable.svt.pincfg[n].value2,5))+1; // CVs zur gültigen Adresse zusammensetzen
sendSensor(Adr, !digitalRead(pinMap[n]));
}
}
}

 

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

// Check for any received LocoNet packets
LnPacket = LocoNet.receive() ;
if( LnPacket )
{

unsigned char opcode = (int)LnPacket->sz.command;
if (opcode == OPC_GPON) {
Serial.println("Power ON"); // Gleisspannung EIN
IOinit_();
}
else if (opcode == OPC_GPOFF) {
Serial.println("Power OFF"); // Gleisspannung AUS
//IOinit();
}

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 )
{
#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;
uint16_t myAddr;

//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) && //Address
(bitRead(svtable.svt.pincfg[n].cnfg,7) == 1)) //Setup as an Output
{
#ifdef DEBUG
Serial.print("Output assigned to port ");
Serial.println(n);
#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);
else
digitalWrite(pinMap[n], LOW);
break;
}
}
}
}

// 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 )
{
#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));

//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
//myAddr=(svtable.svt.pincfg[n].value2 & B00001111)<<7;
//myAddr=myAddr|svtable.svt.pincfg[n].value1;
Serial.print("processPeerPacket READ ");Serial.print(LnPacket->px.d2, DEC);Serial.print(" ");Serial.print(LnPacket->px.d2+1, DEC);Serial.print(" ");Serial.println(LnPacket->px.d2+2, DEC);
#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
svtable.data[LnPacket->px.d2]=LnPacket->px.d4;
EEPROM.write(LnPacket->px.d2,LnPacket->px.d4);

#ifdef DEBUG
Serial.print("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
}

Acht Servos an einem LocoNet-Modul

Die Firmware gründet auf der Entwicklung von Dani Guisado - www.clubncaldes.com.

Innerhalb Rocrail bildet sie die Funktionalität des LocoNet-Bausteiens CGA136 ab. 

Da dieser Baustein fast uneingeschränkt zum Stellen unserer Weichen dient, werden die Servos nach erreichen ihrer jeweiligen Endposition abgeschaltet. Damit wird einerseits die termische Leistung des 7805 enorm reduziert und andererseits die Mechanik des Servos entlastet. 
Die Firmware ist mit der Vers-Nr.: 102 versehen.

Servo 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 Adress 
Aktuell 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 Servomodul markiert ist kann im Konfigurationsbereich "Easy Setup" mit Get All die Konfiguration der Ports ausgelesen werden.

Servo EasySetup In der Titelzeile des Dialogs wird die aktuelle Moduladresse angezeigt.
Um unser Modul als Achtfach-Servo einsetzen zu können müssen die ersten acht Ports als Switch eingestellt werden. Natürlich sollte jeder Port seine eigene Adresse besitzen, sonst kann es zu unerwünschten Adresskonflikten führen.

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. 


 Servo Servo

Nun wird jeder einzelner Servoport eingestellt:

Port auswählen und mit Get die aktuellen Positionen aus dem EPROM des Moduls lesen.
Start- und Endposition werden in den Feldern Pos1 und Pos2 definiert. Der Parameter V steht für die Geschwindigkeit. Hier werden Werte von 0 bis 5 ausgewertet.

Nach einem klick auf Set werden die Daten ins EProm geschrieben. Ber unserer Firmwareversion 102 bewegt sich dann das Ruderhorn mit der vorgegebenen Geschwindikeit von der Start- zur Endposition und wieder zurück.

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

/**************************************************************************
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) attached to two GCA136 to manage
servos.
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,9,10,11 -> Attach one servo motor to each pin
7 -> Loconet TX (connected to GCA185 shield)
8 -> Loconet RX (connected to GCA185 shield)
Configure in Rocrail first 8 ports as output, and last 8 as input
to be used as retro signaling for the switch position
------------------------------------------------------------------------
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

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

#define SVTABLE_MAX_RECORD 125
#define SERVO_LAPSE 10 //millis between servo movements

ServoTimer2 servo[8];

//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[SVTABLE_MAX_RECORD-3];
} SV_TABLE;

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

SV_DATA svtable;
lnMsg *LnPacket;

//This table contains the addresses already transformed in a decimal value
uint16_t directions[16];

int servoCurrentPos[8]; //current position of each servo
int servopin[8] = {2,3,4,5,6,9,10,11};

void setup()
{
int n;

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

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

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

//Load right addresses moving the right bits
for (n=0;n<16;n++)
{
//TODO set right addresses for inputs
directions[n]=svtable.svt.pincfg[n].value1;
bitWrite(directions[n],7,bitRead(svtable.svt.pincfg[n].value2,0));
bitWrite(directions[n],8,bitRead(svtable.svt.pincfg[n].value2,1));
bitWrite(directions[n],9,bitRead(svtable.svt.pincfg[n].value2,2));
}


//Attacch Servos
/*
servo[0].attach(2);
servo[1].attach(3);
servo[2].attach(4);
servo[3].attach(5);
servo[4].attach(6);
servo[5].attach(9);
servo[6].attach(10);
servo[7].attach(11);
*/
// Ä Oestreicher Servopins aus einem Array zuweisen, damit sie abgeschaltet werden können, wenn die Endpositionen erreicht sind
servo[0].attach(servopin[0]);
servo[1].attach(servopin[1]);
servo[2].attach(servopin[2]);
servo[3].attach(servopin[3]);
servo[4].attach(servopin[4]);
servo[5].attach(servopin[5]);
servo[6].attach(servopin[6]);
servo[7].attach(servopin[7]);

pinMode(13, OUTPUT);

//Check for a valid config
if (svtable.svt.vrsion!=VERSION)
{
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);

//Center servos if no previous configuration
for (n=0;n<8;n++)
{
servoCurrentPos[n]=63;
positionServo(n,63);
}
}
else
{
//Position servos and set retro signals
for (n=0;n<8;n++)
{
servoCurrentPos[n]=svtable.data[101+n*3];
positionServo(n,servoCurrentPos[n]);
bitWrite(svtable.svt.pincfg[n+8].value2,4,0);
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[n+8].value1, svtable.svt.pincfg[n+8].value2);
}
}
}

void loop()
{
// Check for any received LocoNet packets
LnPacket = LocoNet.receive() ;
if( LnPacket )
{
#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();
}
}

/*************************************************************************/
/* SERVO FUNCTIONS */
/*************************************************************************/
// moves a servo transforming the angle 0-127 to pulses value range needed by the library
void positionServo(int pServoNum, int pPosition)
{
int val=0;
int lncvnum;

val=map(pPosition,1, 127, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
servo[pServoNum].write(val);
}

// moves a servo according to the servoDestPos[#servo]
// and the configured speed
void moveServo(int pNumServo, int pDestPos)
{
int grades;
int steps;

//if servo already in desired position exit
if (pDestPos==servoCurrentPos[pNumServo]) return;

servo[pNumServo].attach(servopin[pNumServo]); // Ä Oestreicher Servopin verbinden, Servo kann bewegt werden
digitalWrite(13, HIGH);

//read configuration servo speed 0 - 5
steps=5-svtable.data[103+pNumServo*3];

if (servoCurrentPos[pNumServo]<pdestpos)
{
// increment grades
for (grades=servoCurrentPos[pNumServo];grades<=pDestPos;grades++)
{
positionServo(pNumServo,grades);
delay(SERVO_LAPSE*steps);
}
bitWrite(svtable.svt.pincfg[pNumServo+8].value2,4,1);
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[pNumServo+8].value1, svtable.svt.pincfg[pNumServo+8].value2);
}
else
{
// decrement grades
for (grades=servoCurrentPos[pNumServo];grades>=pDestPos;grades--)
{
positionServo(pNumServo,grades);
delay(SERVO_LAPSE*steps);
}
bitWrite(svtable.svt.pincfg[pNumServo+8].value2,4,0);
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[pNumServo+8].value1, svtable.svt.pincfg[pNumServo+8].value2);
}
servoCurrentPos[pNumServo]=pDestPos;

servo[pNumServo].detach(); // Ä Oestreicher Servopin entfernen, Servo ruht
digitalWrite(13, LOW);
}

/*************************************************************************/
/* LOCONET FUNCTIONS */
/*************************************************************************/
void notifyPower( uint8_t State )
{
int n;

#ifdef DEBUG
Serial.print("POWER: ");
Serial.println( State ? "ON" : "OFF" );
#endif
if (State)
{
for (n=0;n<8;n++)
LocoNet.send(OPC_INPUT_REP, svtable.svt.pincfg[n+8].value1, svtable.svt.pincfg[n+8].value2);
}
}

// This call-back function is called from LocoNet.processSwitchSensorMessage
// for all Sensor messages
void notifySensor( uint16_t Address, uint8_t State )
{
#ifdef DEBUG
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;

//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<8; n++)
{
//if ((svtable.svt.pincfg[n].value1 == Address-1) && //Address
if ((directions[n] == Address-1) &&
(bitRead(svtable.svt.pincfg[n].cnfg,7) == 1)) //Setup as an Output
{
//If continue and software reset, one Direction ON turns on and other Direction ON turns off
//OFF messages are not listened
if (bitRead(svtable.svt.pincfg[n].cnfg,3)==0 && bitRead(svtable.svt.pincfg[n].cnfg,2)==0 && Output)
{

if (!Direction)
{
//Servo to one side
moveServo(n,svtable.data[101+3*n]);
}
else
{
//Servo to the other side
moveServo(n,svtable.data[102+3*n]);
}
break;
}
}
}
}

// 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 )
{
#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
}

/*************************************************************************/
/* SV TABLE FUNCTIONS */
/*************************************************************************/
boolean processPeerPacket()
{

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

#ifdef DEBUG
Serial.println("<< OPC_PEER_XFER received...");
#endif

//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));

//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
delay(50);
sendPeerPacket(svtable.data[LnPacket->px.d2], svtable.data[LnPacket->px.d2+1], svtable.data[LnPacket->px.d2+2]);
#ifdef DEBUG
Serial.println(">> OPC_PEER_XFER answer sent");
Serial.println("=============================================");
#endif
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
svtable.data[LnPacket->px.d2]=LnPacket->px.d4;
EEPROM.write(LnPacket->px.d2,LnPacket->px.d4);

//set servo position if servo config command
if (LnPacket->px.d2==103)
{
moveServo(0,svtable.data[101]);
delay(1000);
moveServo(0,svtable.data[102]);
}
if (LnPacket->px.d2==106)
{
moveServo(1,svtable.data[104]);
delay(1000);
moveServo(1,svtable.data[105]);
}
if (LnPacket->px.d2==109)
{
moveServo(2,svtable.data[107]);
delay(1000);
moveServo(2,svtable.data[108]);
}
if (LnPacket->px.d2==112)
{
moveServo(3,svtable.data[110]);
delay(1000);
moveServo(3,svtable.data[111]);
}
if (LnPacket->px.d2==115)
{
moveServo(4,svtable.data[113]);
delay(1000);
moveServo(4,svtable.data[114]);
}
if (LnPacket->px.d2==118)
{
moveServo(5,svtable.data[116]);
delay(1000);
moveServo(5,svtable.data[117]);
}
if (LnPacket->px.d2==121)
{
moveServo(6,svtable.data[119]);
delay(1000);
moveServo(6,svtable.data[120]);
}
if (LnPacket->px.d2==124)
{
moveServo(7,svtable.data[122]);
delay(1000);
moveServo(7,svtable.data[123]);
}

#ifdef DEBUG
Serial.print("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
delay(50);
sendPeerPacket(0x00, 0x00, LnPacket->px.d4);
#ifdef DEBUG
Serial.println(">> OPC_PEER_XFER answer sent");
Serial.println("=============================================");
#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
}