Tutorial - Robuster Ansatz

Mit dem neuen Protokoll 2.0 ist es möglich Programme zu schreiben, die robust gegenüber Unterbrechungen, kurzen Stromausfällen und ähnlichem sind.

Der generelle Ansatz für ein solches Programm sieht aus wie folgt (Pseudocode):

func enumerate_callback(...) {
    configure_brick();
    configure_bricklet();
}

func connected_callback(...) {
    ipcon.enumerate();
}

func main() {
    ipcon.enumerate();

    while (true) {
        if (brick_is_configured) {
            do_something_with_brick();
        }

        if (bricklet_is_configured) {
            do_something_with_bricklet();
        }
    }
}

Es muss sichergestellt werden, dass die Konfiguration von Bricks und Bricklets während der Enumerierung stattfindet. Dies führt dazu, dass die Konfiguration (z.B. Callback-Periode) immer gesetzt wird, auch wenn ein Brick oder Bricklet neu gestartet wurde und dadurch seine Konfiguration verloren hat.

Eine Möglichkeit um dies zu realisieren ist, den Konfigurationscode in das Enumeration-Callback zu schreiben. Es sollte zusätzlich sichergestellt sein, dass eine neue Enumerierung ausgelöst wird, wenn eine TCP/IP-Verbindung neu aufgebaut wird, nachdem sie getrennt wurde. Wenn eine Verbindung getrennt und wiederhergestellt wird, ist nicht ausgeschlossen, dass ein Brick oder Bricklet in der Zwischenzeit neu gestartet wurde. Deshalb muss es auch dann neu konfiguriert werden.

Im Folgenden finden sich C#- und Python-Quelltexte für ein Programm, dass eine Temperatur auf einem LCD 20x4 Bricklet anzeigt. Dieses Programm sollte auch bei einem Neustart des Master Bricks oder einer verlorenen WLAN-Verbindung weiter funktionieren. Es ist sogar möglich, das Temperature oder LCD 20x4 Bricklet auszutauschen, da das Programm die UID aus der Enumerierung benutzt.

C#

Download (ExampleRugged.cs)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
using Tinkerforge;

// This class will use any LCD Bricklet and Temperature Bricklet that
// are connected to the PC and display the temperature on the LCD.
//
// The program should stay stable if Bricks are connected/disconnected,
// if the Brick Daemon is restarted or if a Wi-Fi/RS485 connection is lost.
// It will also keep working if you exchange the Master or one of the
// Bricklets by a new one of the same type.
//
// If a Brick or Bricklet loses its state (e.g. callback configuration)
// while the connection was lost, it will automatically be reconfigured
// accordingly.
class ExampleRugged
{
    private static string HOST = "localhost";
    private static int PORT = 4223;

    private static IPConnection ipcon = null;
    private static BrickletLCD20x4 lcd = null;
    private static BrickletTemperature temp = null;

    static void Main() 
    {
        // Create IP Connection
        ipcon = new IPConnection();

        // Register IP Connection callbacks
        ipcon.EnumerateCallback += EnumerateCB;
        ipcon.Connected += ConnectedCB;

        // Connect to brickd, will trigger cb_connected
        ipcon.Connect(HOST, PORT); 
        ipcon.Enumerate();

        System.Console.WriteLine("Press enter to exit");
        System.Console.ReadLine();
        ipcon.Disconnect();
    }

    // Callback updates temperature displayed on lcd
    static void TemperatureCB(BrickletTemperature sender, short temperature)
    {
        if(lcd != null)
        {
            lcd.ClearDisplay();
            string s = "Temperature: " + temperature/100.0 + (char)0xdf + "C";
            lcd.WriteLine(0, 0, s);
        }
    }

    // Callback switches lcd backlight on/off based on lcd button 0
    static void ButtonPressedCB(BrickletLCD20x4 sender, byte button)
    {
        if(lcd != null)
        {
            if(button == 0)
            {
                if(lcd.IsBacklightOn())
                {
                    lcd.BacklightOff();
                }
                else
                {
                    lcd.BacklightOn();
                }
            }
        }
    }

    // Callback handles device connections and configures possibly lost
    // configuration of lcd and temperature callbacks, backlight etc.
    static void EnumerateCB(IPConnection sender, string UID, string connectedUID, 
                            char position, short[] hardwareVersion, 
                            short[] firmwareVersion, int deviceIdentifier, 
                            short enumerationType)
    {
        if(enumerationType == IPConnection.ENUMERATION_TYPE_CONNECTED ||
           enumerationType == IPConnection.ENUMERATION_TYPE_AVAILABLE)
        {
            // Enumeration is for LCD Bricklet
            if(deviceIdentifier == BrickletLCD20x4.DEVICE_IDENTIFIER)
            {
                // Create lcd device object
                lcd = new BrickletLCD20x4(UID, ipcon);
                lcd.ButtonPressed += ButtonPressedCB;

                lcd.ClearDisplay();
                lcd.BacklightOn();
            }
            // Enumeration is for Temperature Bricklet
            if(deviceIdentifier == BrickletTemperature.DEVICE_IDENTIFIER)
            {
                // Create temperature device object
                temp = new BrickletTemperature(UID, ipcon);
                temp.Temperature += TemperatureCB;

                temp.SetTemperatureCallbackPeriod(50);
            }
        }
    }

    // Callback handles reconnection of IP Connection
    static void ConnectedCB(IPConnection sender, short connectReason)
    {
        // Enumerate devices again. If we reconnected, the Bricks/Bricklets
        // may have been offline and the configuration may be lost.
        // In this case we don't care for the reason of the connection
        ipcon.Enumerate();
    }
}

Python

Download (example_rugged.py)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/env python
# -*- coding: utf-8 -*-  

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_lcd_20x4 import LCD20x4
from tinkerforge.bricklet_temperature import Temperature

# This class will use any LCD Bricklet and Temperature Bricklet that
# are connected to the PC and display the temperature on the LCD.
#
# The program should stay stable if Bricks are connected/disconnected,
# if the Brick Daemon is restarted or if a Wi-Fi/RS485 connection is lost.
# It will also keep working if you exchange the Master or one of the
# Bricklets by a new one of the same type.
#
# If a Brick or Bricklet loses its state (e.g. callback configuration)
# while the connection was lost, it will automatically be reconfigured
# accordingly.
class ExampleRugged:
    HOST = "localhost"
    PORT = 4223

    def __init__(self):
        self.lcd = None
        self.temp = None

        # Create IP Connection
        self.ipcon = IPConnection() 

        # Register IP Connection callbacks
        self.ipcon.register_callback(IPConnection.CALLBACK_ENUMERATE, 
                                     self.cb_enumerate)
        self.ipcon.register_callback(IPConnection.CALLBACK_CONNECTED, 
                                     self.cb_connected)

        # Connect to brickd, will trigger cb_connected
        self.ipcon.connect(ExampleRugged.HOST, ExampleRugged.PORT) 

        self.ipcon.enumerate()

    # Callback switches lcd backlight on/off based on lcd button 0
    def cb_button_pressed(self, button):
        if self.lcd:
            if button == 0:
                if self.lcd.is_backlight_on():
                    self.lcd.backlight_off()
                else:
                    self.lcd.backlight_on()

    # Callback updates temperature displayed on lcd
    def cb_temperature(self, temperature):
        if self.lcd:
            self.lcd.clear_display()
            s = 'Temperature: {0:.2f}{1:c}C'.format(temperature/100.0, 0xdf)
            self.lcd.write_line(0, 0, s)

    # Callback handles device connections and configures possibly lost 
    # configuration of lcd and temperature callbacks, backlight etc.
    def cb_enumerate(self, uid, connected_uid, position, hardware_version, 
                     firmware_version, device_identifier, enumeration_type):
        if enumeration_type == IPConnection.ENUMERATION_TYPE_CONNECTED or \
           enumeration_type == IPConnection.ENUMERATION_TYPE_AVAILABLE:
            
            # Enumeration is for LCD Bricklet
            if device_identifier == LCD20x4.DEVICE_IDENTIFIER:
                # Create lcd device object
                self.lcd = LCD20x4(uid, self.ipcon) 
                self.lcd.register_callback(self.lcd.CALLBACK_BUTTON_PRESSED, 
                                           self.cb_button_pressed)
                self.lcd.clear_display()
                self.lcd.backlight_on()
            # Enumeration is for Temperature Bricklet
            if device_identifier == Temperature.DEVICE_IDENTIFIER:
                # Create temperature device object
                self.temp = Temperature(uid, self.ipcon) 
                self.temp.register_callback(self.temp.CALLBACK_TEMPERATURE, 
                                            self.cb_temperature)

                self.temp.set_temperature_callback_period(50)

    # Callback handles reconnection of IP Connection
    def cb_connected(self, connected_reason):
        # Enumerate devices again. If we reconnected, the Bricks/Bricklets
        # may have been offline and the configuration may be lost.
        # In this case we don't care for the reason of the connection
        self.ipcon.enumerate()
        

if __name__ == "__main__":
    ExampleRugged()
    raw_input('Press key to exit\n') # Use input() in Python 3