Forum: Projekte & Code Weichensteuerung für Modelleisenbahn mit Arduino, Raspberry Pi und MQTT Server


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Julius M. (juliusmerseburger)


Lesenswert?

Ich arbeite momentan an meinem ersten "größeren" 
Software/Hardware-Projekt und möchte diesen Foreneintrag nutzen um 
dieses Projekt vorzustellen und eventuell Kritik, Anregungen und 
Ratschläge dazu zu erhalten.

Für eine in Modulbauweise konstruierte Modelleisenbahn möchte ich eine 
intelligente Weichensteuerung erstellen und diese soll grob umrissen wie 
folgt funktionieren:

-auf einem Schaltpult ist der Plan des Bahnhofs aufgedruckt
-jeweils an allen Ein/Ausfahrten gibt es einen Start-Taster und an jedem 
Gleis gibt es ebenso einen Zieltaster
-unter jedem Modul des Bahnhofs befindet sich ein Arduino mit Ethernet 
Shield
-an diese Arduinos ist jeweils ein PCA9685 PWM Servo Controller 
angeschlossen
-dieser Servocontroller steuert logischerweise Servomotoren welche 
schlussendlich das eigentliche Stellen der Weichen übernehmen
-ein Raspberry Pi soll die Tastendrücke aufnehmen und wenn eine richtige 
Kombination aus Start-Zieltaster gedrückt wurde nacheinander ein Signal 
an einen MQTT-Broker, welcher auf selbigem Raspberry Pi läuft, schicken 
um die entsprechenden Weichen zu stellen
-die Arduinos sind über einen Switch mit dem Raspberry verbunden und 
reagieren auf die entsprechenden Steuer-Anweisungen und geben eine 
Rückmeldung sobald der Befehl ausgeführt wurde

Aktueller Stand des Ganzen ist, ich habe den MQTT Broker zum laufen 
gebracht und auf dem Arduino bereits einen laufenden Code der sich in 
dem Mini-Netzwerk anmeldet, mit dem MQTT-Broker verbindet, im Code 
vorgegebene Themen abonniert und auf eingehende Nachrichten reagiert. 
Ich arbeite gerade daran den Code lesbarer und "hübscher" zu gestalten, 
genauer gesagt versuche ich den Arduino-Code objektorientierter 
umzusetzen, und den PWM-Controller in das Ganze einzubinden.

Da dies mein erster Foren-Eintrag ist gebt mir gerne Tipps, übt Kritik 
oder stellt fragen wenn euch das Projekt interessiert.

Main:
1
#include "HCPCA9685.h"
2
#include "SETUP.h"
3
4
void setup() {
5
  Serial.begin(9600);
6
  ethernetSetup();
7
  mqttSetup();
8
  PWMSetup();
9
10
  // put your setup code here, to run once:
11
  
12
}
13
14
15
void loop() {
16
  // put your main code here, to run repeatedly:
17
  mqttLoop();
18
}

Setup:
1
#include "SPI.h"
2
#include "Ethernet.h"
3
#include "PubSubClient.h"
4
#include "Weiche.h"
5
6
7
constexpr auto I2CAdd1 = 0x40;
8
constexpr auto I2CAdd2 = 0x41;
9
HCPCA9685 PWMController1(I2CAdd1); 
10
11
Weiche W16(160,240,2000,1);
12
Weiche W17(120,280,2000,2);
13
Weiche W18(120,280,2000,3);
14
Weiche W19(120,280,2000,4);
15
Weiche W20(120,280,2000,5);
16
17
EthernetClient ethernetClient;
18
const byte mac[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA};
19
const IPAddress deviceIP (255,255,255,255);
20
const IPAddress dns (255,255,255,255);
21
const IPAddress gateway (255,255,255,255);
22
const IPAddress subnet (255,255,255,255);
23
const char* deviceID = "Modul1";
24
25
PubSubClient MQTTclient(ethernetClient);
26
const IPAddress mqttServerIP(255,255,255,255);
27
const char sub[][10]={{"W16/Soll"},{"W17/Soll"},};
28
const int subcnt = 2;
29
long lastMsgTime = 0;    
30
char msg[64];               // A buffer to hold messages to be sent/have been received
31
char topic[32];             // The topic in which to publish a message
32
int pulseCount = 0;         // Counter for number of heartbeat pulses sent
33
34
35
void PWMSetup() {
36
    PWMController1.Init(SERVO_MODE);
37
    PWMController1.Sleep(false);
38
}
39
40
41
void ethernetSetup() {
42
  Ethernet.init(10);
43
  Ethernet.begin(mac, deviceIP, dns, gateway, subnet);
44
  Serial.println("ich lebe");
45
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
46
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
47
      while (true) {
48
        delay(1); // do nothing, no point running without Ethernet hardware
49
      }
50
    }
51
  delay(2000);
52
53
  // Print debug info about the connection 
54
  Serial.print("Connected! IP address: ");
55
  Serial.println(Ethernet.localIP());
56
57
}
58
59
void mqttCallback(char* topic, byte* payload, unsigned int length) {
60
61
  // The message "payload" passed to this function is a byte*
62
  // Let's first copy its contents to the msg char[] array
63
  int wnr = (int(topic[1])-'0')*10+int(topic[2])-'0';
64
  memcpy(msg, payload, length);
65
  // Add a NULL terminator to the message to make it a correct string
66
  msg[length] = '\0';
67
68
  // Debug
69
  Serial.print("Message received in topic [");
70
  Serial.print(topic);
71
  Serial.print("] ");
72
  Serial.println(msg);
73
74
  // Act upon the message received
75
  switch (wnr){
76
    case 16:  if(strcmp(msg,"Rechts") == 0){
77
                W16.stellen(rechts);
78
                Serial.println(W16.get_Zustand());
79
                break;
80
              }
81
              if(strcmp(msg,"Links") == 0){
82
                W16.stellen(links);
83
                Serial.println(W16.get_Zustand());
84
                break;
85
              }
86
              else {
87
                Serial.println("Falscher Befehl");
88
                break;
89
              }
90
    case 17: if(strcmp(msg,"Rechts") == 0){
91
                W17.stellen(rechts);
92
                Serial.println(W16.get_Zustand());
93
                break;
94
              }
95
              if(strcmp(msg,"Links") == 0){
96
                W17.stellen(links);
97
                Serial.println(W16.get_Zustand());
98
                break;
99
              }
100
              else {
101
                Serial.println("Falscher Befehl");
102
                break;
103
              }
104
    default: break;
105
    }
106
}
107
108
void mqttSetup() {
109
  MQTTclient.setServer(mqttServerIP, 1883);
110
  MQTTclient.setCallback(mqttCallback);
111
}
112
113
void mqttLoop () {
114
  while (!MQTTclient.connected()) {     // Ensure there's a connection to the MQTT server
115
    Serial.print("Attempting to connect to MQTT broker at ");
116
    Serial.println(mqttServerIP);
117
    
118
    if (MQTTclient.connect(deviceID)) {           // Attempt to connect
119
      // Debug info
120
      Serial.println("Connected to MQTT broker");
121
      
122
      for (int i=0; i<2; i++){
123
          snprintf(topic, 32, sub[i]);
124
          MQTTclient.subscribe(topic);
125
          Serial.println("subscribed to");
126
          Serial.println(topic);
127
      }               
128
    }
129
    else {
130
      // Debug info why connection could not be made
131
      Serial.print("Connection to MQTT server failed, rc=");
132
      Serial.print(MQTTclient.state());
133
      Serial.println(" trying again in 5 seconds");
134
      
135
      // Wait 5 seconds before retrying
136
      delay(5000);
137
    }
138
  }
139
  
140
  // Call the main loop to check for and publish messages
141
  MQTTclient.loop();
142
}

Weiche.h:
1
#include "Zustand.h"
2
#include "HCPCA9685.h"
3
4
class Weiche{
5
  private:
6
    int winkelRechts;
7
    int winkelLinks;
8
    int stellzeit;
9
    int steckplatz;
10
    Zustand alt;
11
12
  public:
13
    Weiche(int wr, int wl, int wz, int sp);
14
    int get_WinkelRechts();
15
    int get_WinkelLinks();
16
    int get_Stellzeit();
17
    int get_Steckplatz();
18
    Zustand get_Zustand();
19
    
20
21
    void set_WinkelRechts(int wr);
22
    void set_WinkelLinks(int wl);
23
    void set_Stellzeit(int sz);
24
    void set_Steckplatz(int sp);
25
    void set_Zustand(Zustand neu);
26
27
    void stellen(Zustand a);
28
};

Weiche.cpp:
1
#include "Weiche.h"
2
3
4
Weiche::Weiche(int wr, int wl, int sz, int sp){
5
    winkelRechts = wr;
6
    winkelLinks = wl;
7
    stellzeit = sz;
8
    steckplatz = sp;
9
    alt = unbekannt;
10
}
11
12
    int Weiche::get_WinkelRechts()            {return winkelRechts;}
13
    int Weiche::get_WinkelLinks()             {return winkelLinks;}
14
    int Weiche::get_Stellzeit()               {return stellzeit;}
15
    int Weiche::get_Steckplatz()                      {return steckplatz;}
16
    Zustand Weiche::get_Zustand()             {return alt;}
17
    
18
19
    void Weiche::set_WinkelRechts(int wr)     {winkelRechts = wr;}
20
    void Weiche::set_WinkelLinks(int wl)      {winkelLinks = wl;}
21
    void Weiche::set_Stellzeit(int sz)        {stellzeit = sz;}
22
    void Weiche::set_Zustand(Zustand neu)     {alt = neu;}
23
    void Weiche::set_Steckplatz(int sp)               {steckplatz = sp;}
24
    
25
    void Weiche::stellen(Zustand neu){
26
      if(this->alt==neu){
27
        return;
28
      }
29
      else{
30
        this->alt=neu;
31
        return;
32
      }
33
    }

Zustand.h:
1
enum Zustand{
2
  unbekannt, rechts, links
3
};

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Julius M. schrieb:
> unter jedem Modul des Bahnhofs befindet sich ein Arduino mit Ethernet
> Shield

Meinst du nicht, dass das ein irrsinniger Aufwand ist ?

Digitale Modelleisenbahn ist erfunden, mit primitiver Elektronik an 
jeder Weiche, es reicht ein Attiny8 mit 2 Transistoren, der seine 
Steuersignale übers Gleis erhält

Dein Fahrstrassenkonzept hat ein Bekannter auf einem Tablet 
programmiert, das dann per WIFi mit einem ESP am Booster redet.

Man braucht nicht dutzende teurer Module, die kein Problem 
(Wechselstromversorgung, Konzept) lösen, sondern nur neue Probleme 
schaffen.

von Julius M. (juliusmerseburger)


Lesenswert?

Also dass der Aufwand irrsinnig ist und dass das 
Preis-Leistungs-Verhältnis irrsinnig schlecht ist, ist mir durchaus 
bewusst. Hab aber grad viel Spaß und jede Menge Motivation die ganzen 
Komponenten zu verstehen und auf allen Ebenen (Taster bis Netzwerk) zu 
arbeiten. Hab jetzt schon eine ganze Menge gelernt und empfinde es als 
überaus befriedigend wenn ich nach mehreren Stunden ein kleines 
Teilproblem gelöst habe.
Ein kleiner Bonuspunkt für meine extrem aufwendige Lösung ist, dass ich 
das ganze in Zukunft relativ leicht erweitern kann (Display im 
Stellpult, zeitgesteuerte Schaltungen, Steuerung per APP usw).

: Bearbeitet durch User
von MaWin (Gast)


Lesenswert?

Julius M. schrieb:
> Ein kleiner Bonuspunkt für meine extrem aufwendige Lösung ist, dass ich
> das ganze in Zukunft relativ leicht erweitern kann

Na offenbar eher nicht.

Man muss für Erweiterungen bei dir erst neue Hardware bauen, dann alle 
alte daran anpassen dass nun z.B. ein neues Ziel möglich ist

Mit einem Gleisbildstellwerk auf einem Pad, rein in Software, steckt man 
einfach die digitalsteuerbare Weiche an die Schiene und der Rest ist 
Software. Es wird nicht ein einziges Kabel verlegt.

von Julius M. (juliusmerseburger)


Lesenswert?

Naja an der Anlage selber wird sich nichts mehr ändern, also keine neuen 
Weichen mehr dazu kommen. Die Lösung mit der Steuerung über die Taster 
war eine Bedingung die mir so vorgegeben wurde und musste deswegen in 
das Ganze mit implementiert werden.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.