Mittwoch, 6. Februar 2013

Build-Ampel selbst bauen

Motivation

Selbst für kleine Projekte, an denen nur eine Hand voll Mitarbeiter beteiligt sind, wird man schnell die Vorteile eines Build-Servers schätzen lernen. Eine Funktion eines Build-Servers ist es anzuzeigen, ob das, was man gerade in die Quellcodeverwaltung "commited" hat auch auf einem Fremdsystem gebuildet werden kann. Zur Anzeige des Build-Status auf dem Entwicklungsrechner werden oft Programme verwendet, die den Status als Ampel-Icon im Tray anzeigen.

Als Beispiel möchte ich den Build-Server ThoughtWorks Cruise Control .Net erwähnen, hierfür existiert das Tool CCTray, welches ein Tray-Icon zur Verfügung stellt, um den Status anzuzeigen. Da ich mitbekommen habe, dass dieses kleine Icon von Entwicklern oft ignoriert wird, habe ich nach einer anderen Lösung gesucht und auch gefunden. 

Was wäre wenn man diese Software-Ampel aus dem Tray auf eine reale Ampel überträgt !? Naja, vielleicht ist eine echte Verkehrsampel etwas übers Ziel hinaus geschossen, aber da hat mein einjähriger Sohn auf die Richtige Idee gebracht: Für sein Bobby Car hat er eine Spiel-Ampel von BIG, die fürs Erste ganz gut geeignet für meine Anforderung ist.

Fehlt nur noch die Steuerung der Ampel, man könnte hier direkt über ein USB Relais-Interface die einzelnen Lampen an der Ampel ansteuern und schalten. An sich ein probates Mittel, aber so recht keine Herausforderung - ich denke da eher an eine Funk-Fernsteuerung oder übers Netzwerk. Da ich nach langem Suchen keine praktikable Lösung gefunden habe, wollte ich schon fast aufgeben, dann ist mir aber ein Angebot von Pollin Electronic ins Auge gesprungen, der Bausatz AVR-NET-IO. Damit kann man über TCP/IP bis zu 8 Ausgänge schalten, das sollte für 2 Ampeln und 2 Fußgängerampeln genügen.

Einkaufsliste

Bobby Car Ampel von Big (13,99 Euro)
Bausatz: AVR-NET-IO (19,95 Euro)
Lötkolben, Lötzinn, Werkzeug, .. (sollte man zu Hause haben)
Kabel / min. 12-adrig, ca.1 m (zB: billiges Scart-Kabel: ca. 1 Euro)
Netzteil 9V~ min. 200 mA  (ab 2 Euro)

Gesamtpreis: 36,94 Euro (zzgl. Versand)

Umsetzung

Quelle: Wikipedia, http://en.wikipedia.org/wiki/Parallel_port
Zuerst sollte man den AVR-NET-IO Bausatz nach der Anleitung zusammen löten, für uns interessant ist hier prinzipiell der DB-25F Port (J3), dieser entspricht in den Pins 2-13 und den GND-Pins 18-25 einem herkömmlichen LPT Port, was es theoretisch ermöglich hier eine entsprechende parallele Interface-Karte anzuschließen und anzusteuern. Speziell wenn man vor hat größere Ströme zu schalten, macht dies Sinn. Die Leuchtmittel in unserer Spielzeugampel kommen jedoch mit dem Steuerstrom von 5 Volt ganz gut aus, so können wir uns die 15 Euro für die Relaiskarte sparen. Wer es modular aufbauen will, kann eine entsprechende Anschlussplatine kaufen (ca. 4 Euro), um die einzelnen Ausgänge an  die Ampel anzuschließen.

Nachdem man nun die Ampel durch die Schraube an der Unterseite auseinander genommen hat und die Drähte von der Ampel-Steuerplatine trennt, wird man feststellen, dass man mehr Kabel hat, als Adern im Kabel. Jede Lampe an der Ampel hat 2 Adern, die abgehen - dabei handelt es sich zum einen um das GND-Kabel (also die Masse) und zum Anderen um die Phase. Da es sich in gegebenem Fall um Gleichstrom handelt, ist das eine der Pluspol, der andere der Minuspol. Aktuell ist irrelevant, was was ist, denn in unserer Ampel befinden sich Glühbirnen. Die GND-Kabel von den zwei Rot-Gelb-Grün Ampeln können zusammengefasst werden und durch eine Ader zum AVR-Modul geführt werden. Außerdem kann man die Grünen GND-Kabel auch da mit zusammenfassen. Zusätzlich brauchen wir ein "Kabelverbund", der immer Spannung erhält, dazu gehört jeweils ein Kabel der roten Lampe aus den Fußgängerampeln.

Jedes übrige Kabel (also die Restlichen 10) muss in einer separaten Ader zum AVR-IO-Modul geführt werden. In meinem Aufbau habe ich das AVR-IO-Modul unten auf den Sockel der Ampel aufgeschraubt und das Kabel durch die hohle Stange der Ampel geführt.Prinzipiell würde das Modul auch direkt in die Ampel passen (dann würde man sich das 12-Adrige Kabel sparen), aber da das Modul recht heiß wird, finde ich diese Lösung nicht sehr praktikabel.

Nachdem wir nun alle Kabel am Modul haben, können wir uns der Pin-Belegung widmen. Das GND-Kabel wird an eine der GND-Leitungen angeschlossen, wie es am Besten passt. Der andere "Kabelverbund" wird an die High-Leitung (5V), also Pin 15 angeschlossen.

Ampel 1
Grün: Pin 2 (D0)
Gelb: Pin 3 (D1)
Rot: Pin 4 (D2)

Ampel 2
Grün: Pin 5 (D3)
Gelb: Pin 6 (D4)
Rot: Pin 7 (D5)

Nun haben wir noch 4 Adern übrig, aber nur 2 Ausgänge. Wenn man etwas darüber nachdenkt, kommt man auf die Lösung - Die Kabel für rot und grün werden gemeinsam angeschlossen. Da die zweite Ader für Rot auf 5V und die zweite Ader für Grün auf GND liegt, ist die Ausgabe der Fußgängerampel abhängig von der jeweiligen Datenleitung, liegt diese auf GND, so leuchtet sie rot, liegt sie auf HIGH (5V), so leuchtet sie grün.

Ampel 3
Grün: Pin 8 (D6)
Rot: Pin 8 (D6)

Ampel 4
Grün: Pin 9 (D7)
Rot: Pin 9 (D7)

Steuerung & Software

Jetzt könnten wir eigentlich schon mal die Ampel testen, der Anschluss an einen PC erfolgt über ein Cross-Over Netzwerkkabel, am PC muss man dafür einfach die Adaptereinstellungen der Netzwerkkarte so ändern, dass man dem PC eine IP aus dem Subnetz 192.168.0.0/24 zuweist, zum Beispiel:
IP: 192.168.0.100
Subnetzmaske: 255.255.255.0

Die IP-Adresse des AVR-NET-IO ist 192.168.0.90, der TCP-Port für eine Verbindung ist  50290 . Verbinden kann man sich nun mit Hilfe von Telnet oder PuTTY (siehe Beschreibung des Bausatz) und die ersten Befehle testen. Für uns relevant ist lediglich der Befehl SETPORT. Mit Hilfe von SETPORT 1.1 kann man nun zum Beispiel die grüne Lampe an Ampel 1 aufleuchten lassen. Aber Vorsicht beim Testen, wenn ihr keine Relaisplatine verwendet können maximal etwa 6 Lampen gleichzeitig leuchten, sonst bekommt der Mikrocontroller nicht mehr genug Strom, um die Netzwerkkommunikation aufrecht zu erhalten.

Kommen wir nun zur Software in C#, um die Ampel anzusteuern. Die erste Abstraktionsschicht bildet die Klasse AVRNetIO:

    class AVRNetIO
    {
        /// <summary>
        /// Pin Status
        /// </pre>
        public enum AVRNetIOStatus
        {
            /// <summary>
            /// 5V
            /// </summary>
            High=1,
            /// <summary>
            /// GND
            /// </summary>
            Low=0
        }

        static System.Net.Sockets.TcpClient _client = new System.Net.Sockets.TcpClient(Properties.Settings.Default.NetIoHost, Properties.Settings.Default.NetIoPort);

        /// <summary>
        /// sets the Portstatus of a connected AVR-NET-IO modul
        /// </summary>
        /// <param name="pinId" />the pin id (1-8)
        /// <param name="status" />the status (high|low)
        public static void SetOutput(int pinId, AVRNetIOStatus status)
        { 
            if (pinId<1 data-blogger-escaped-pinid="">8)
                throw new NotImplementedException("The AVR-NET-IO only implements 8 output pins (1-8)")
            System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
            string command = "SETPORT "+pinId+"."+(int)status+"\r\n";
            _client.Client.Send(enc.GetBytes(command));
            System.Threading.Thread.Sleep(100);
        }
    }
Nun ist es möglich das Modul übers Netzwerk direkt anzusteuern und die Ausgänge zu schalten. IP und Port sind hier per AppConfig einstellbar.

Kommen wir nun zur nächsten Abstraktionsebene, die Ampel an sich. Dazu gibt es erst einmal einen enum für den Ampelstatus und einen für die Art der Ampel (Fußgänger, Verkehr):

        public enum SignalLightTypes
        {
            TrafficLight,
            PedestrianLight
        }

        public enum SignalLightSignals
        {
            Red=2,
            Yellow=4,
            Green=8
        }

Die eigentliche Klasse für SignalLight wird folgendermaßen implementiert:

    public class SignalLight
    {
        SignalLightTypes _type;
        int _startPin;

        public SignalLight(SignalLightTypes type, int startPinId)
        {
            _startPin = startPinId;
            _type = type;
        }

        void SetStatus(SignalLightSignals signalStatus)
        {
            // we only use 1 pin for the pedestrian light
            if (_type == SignalLightTypes.PedestrianLight)
            {
                 if ((signalStatus & SignalLightSignals.Red) == SignalLightSignals.Red)
                     AVRNetIO.SetOutput(_startPin, AVRNetIO.AVRNetIOStatus.Low);
                 if ((signalStatus & SignalLightSignals.Green) == SignalLightSignals.Green)
                     AVRNetIO.SetOutput(_startPin, AVRNetIO.AVRNetIOStatus.High);
            }
            else
            {
                // startPin : green
                // startPin+1 : yellow
                // startPin+2 : red
                AVRNetIO.AVRNetIOStatus redStatus = AVRNetIO.AVRNetIOStatus.Low;
                AVRNetIO.AVRNetIOStatus yellowStatus = AVRNetIO.AVRNetIOStatus.Low;
                AVRNetIO.AVRNetIOStatus greenStatus = AVRNetIO.AVRNetIOStatus.Low;
                if ((signalStatus & SignalLightSignals.Red) == SignalLightSignals.Red)
                    redStatus = AVRNetIO.AVRNetIOStatus.High;
                if ((signalStatus & SignalLightSignals.Yellow) == SignalLightSignals.Yellow)
                    yellowStatus = AVRNetIO.AVRNetIOStatus.High;
                if ((signalStatus & SignalLightSignals.Green) == SignalLightSignals.Green)
                    greenStatus = AVRNetIO.AVRNetIOStatus.High;
                int pinId = _startPin;
                AVRNetIO.SetOutput(pinId, greenStatus);
                AVRNetIO.SetOutput(++pinId, yellowStatus);
                AVRNetIO.SetOutput(++pinId, redStatus);
            }
        }

        private SignalLightSignals _currentStatus;

        /// <summary>
        ///  gets/sets the current status of the signal light
        /// </summary>
        public SignalLightSignals CurrentStatus
        {
            get { return _currentStatus; }
            set { 
                _currentStatus = value;
                SetStatus(value);
            }
        }

    }
Kein Hexenwerk, je nachdem welcher enum als CurrentStatus gesetzt wird, wird der / die entsprechenden Pins auf High gesetzt. Bei Fußgängerampeln ist es ein kleiner Sonderfall, hier wird nur ein Pin verwendet. Bei rot wird dieser auf Low und bei grün auf High geschalten. Initial sind die Fußgängerampeln immer auf rot, denn die Pins vom AVR-Net-IO sind initial auf Low geschalten.

Wir könnten jetzt anhand dessen eine "sinnvolle" Ampelsteuerung entwickeln, das wollen wir aber nicht, denn der Initiale Gedanke war es, eine Build-Ampel zu entwickeln. Ich habe anfangs von CCTray bzw. CC.Net gesprochen, daher will ich jetzt eine Beispielimplementierung für CC.Net zeigen. Dazu brauchen wir die Datei ThoughtWorks.CruiseControl.Remote.dll aus dem Setup von CCTray, da ich nicht weiß in wie weit es Distributionsbeschränkungen gibt, gebe ich hier nur den Namen der Datei an, der Download muss dann direkt von der Seite von CC.Net geschehen (im Bundle mit der Software CCTray)

Um nun den Status aus CruiseControl.Net auszulesen, müssen wir eine neue Instanz von CruiseServerHttpClient erstellen, damit können wir nun den Projektstatus von jedem Projekt auslesen (GetProjectStatus()) und in den Ampelstatus umwandeln.


// initiate signal light classes
SignalLight _signal1 = new SignalLight(SignalLight.SignalLightTypes.TrafficLight, 1);
SignalLight _signal2 = new SignalLight(SignalLight.SignalLightTypes.TrafficLight, 4);
SignalLight _signal3 = new SignalLight(SignalLight.SignalLightTypes.PedestrianLight, 7);
SignalLight _signal4 = new SignalLight(SignalLight.SignalLightTypes.PedestrianLight, 8);

// get variables from config
var ipAddressOrHostNameOfCCServer = Settings.Default.buildserver; 
string project1 = Settings.Default.project1;
string project2 = Settings.Default.project2;
string project3 = Settings.Default.project4;
string project4 = Settings.Default.project4;
var client = new CruiseServerHttpClient(
    string.Format("http://{0}/ccnet/", ipAddressOrHostNameOfCCServer));

foreach (var projectStatus in client.GetProjectStatus())
{ // iterate through projects
    SignalLight.SignalLightSignals status;

    // convert project's status to SignalLightsSignals
    // building -> yellow, success -> green, failure -> red, everything else -> yellow
    if (projectStatus.Activity == ProjectActivity.Building)
        status = SignalLight.SignalLightSignals.Yellow;
    else if (projectStatus.BuildStatus == IntegrationStatus.Success)
        status = SignalLight.SignalLightSignals.Green;
    else if (projectStatus.BuildStatus == IntegrationStatus.Failure || projectStatus.BuildStatus == IntegrationStatus.Exception)
        status = SignalLight.SignalLightSignals.Red;
    else
        status = SignalLight.SignalLightSignals.Yellow;

    // set status on appropriate project
    if (projectStatus.Name == project1)
    {
        _signal1.CurrentStatus = status;
    }
    if (projectStatus.Name == project2)
    {
        _signal2.CurrentStatus = status;
    }
    if (projectStatus.Name == project3)
    {
        _signal3.CurrentStatus = status;
    }
    if (projectStatus.Name == project4)
    {
        _signal4.CurrentStatus = status;
    }
}

Bitte seht mir nach, dass ich momentan keine Bilder habe, sobald ich Zeit finde, werde ich ein paar Bilder machen und diese posten.

Mittwoch, 24. Oktober 2012

SQL Code Snippet: Anzeigen aller durch Fremdschlüssel abhängigen Tabellen

Mit diesem MS SQL-Script ist es möglich zu einer gegebenen Tabelle alle Tabellen zu finden, von denen diese durch Foreign Key Constraints abhängig ist.
declare @TableName nvarchar(50)='TestTable2'
declare @SchemaName nvarchar(10)='dbo'

 select schRef.name+'.'+referenced.name from sys.foreign_keys fk
 join sys.tables referenced on referenced.object_id = fk.referenced_object_id
 join sys.tables base on base.object_id = fk.parent_object_id
 join sys.schemas schBase on schBase.schema_id = base.schema_id
 join sys.schemas schRef on schRef.schema_id = referenced.schema_id
 where base.name=@TableName and schBase.name=@SchemaName
um das ganze jetzt auch noch vollständig ausgeben zu können (inkl. Column), wäre folgendes der Code:
declare @TableName nvarchar(50)='TestTable2'
declare @SchemaName nvarchar(10)='dbo'

  select 
 schRef.name+'.'+referenced.name  ReferencedTable
 , colRef.name      ReferencedColumn
 , schBase.name+'.'+base.name  BaseTable
 ,colBase.name      BaseColumn
 from sys.foreign_keys fk
  -- ref table
 join sys.tables referenced on referenced.object_id = fk.referenced_object_id
 join sys.schemas schRef on schRef.schema_id = referenced.schema_id
 
 -- base table
 join sys.tables base on base.object_id = fk.parent_object_id
 join sys.schemas schBase on schBase.schema_id = base.schema_id
 
 -- columns
 join sys.foreign_key_columns fkc on fkc.constraint_object_id = fk.object_id
 
 join sys.all_columns colBase on colBase.column_id = fkc.parent_column_id and colBase.object_id = base.object_id
 join sys.all_columns colRef on colRef.column_id = fkc.referenced_column_id and colRef.object_id = referenced.object_id


 where base.name=@TableName and schBase.name=@SchemaName

Samstag, 13. Oktober 2012

Große XML Datei in mehrere kleine Dateien aufteilen

Problembeschreibung


Wer viel mit XML-Dateien zu tun hat, kennt vielleicht das Problem - man hat gerade im Programm (oder aus einer Datenbank) ein XML erzeugt, das witzige 50 MB groß ist. Eigentlich in der heutigen Zeit von Terabyte-Festplatten kein großer Wert, sollte man meinen, aber Visual Studio hängt sich beim Öffnen auf. Auch Notepad++ hat hier selbst mit SSD bedenkliche Probleme. Einzig das gute alte Notepad von Windows kann die Datei zumindest anzeigen - das war's aber dann auch, denn damit kann ich nicht wirklich viel anfangen, immerhin sind 50MB auf einer Zeile nicht wirklich übersichtlich.

Lösung

Ich habe mir ein kleines Programm geschrieben, dass ein Eingabe XML in mehrere kleinere XML aufteilt. Die Herausforderung lag darin die Datei nicht komplett einzulesen, sondern Knoten für Knoten zu verarbeiten. Außerdem musste sich das Porgramm merken, welche Knoten noch geöffnet sind, um diese dann zu schließen, sobald die maximale Anzahl an Bytes erreicht wurde. Dadurch ist gewährleistet, dass die resultierenden XML-Dateien auch valides XML repräsentieren.
 

Implementierung

Im Prinzip verwende ich für das Lesen der XML-Datei einen XmlTextReader , das hat den entscheidenden Vorteil, dass die XML-Elemente einzeln eingelesen werden und man XML spezifische Metainformationen zu jedem gelesenen Element erhält. Ich spar mir im Code das kopieren der Kommentare und der XML-Deklaration, wenn das jemand benötigt, ist das schnell zu ergänzen. Außerdem verzichte ich auf die Berücksichtigung von Namespaces.
 
 Der  Programmablauf ist recht einfach:
- Lese jeden Tag und puffere diesen
- öffnende Tags werden auf einen Stack gelegt
- bei schließenden Tags wird der letzte öffnende Tag vom Stack entfernt
- hat der Puffer die gewünschte Größe, so schließe alle noch offenen Tags und schreibe den Puffer auf Platte
- leere Puffer
- öffne die zuletzt offenen Tags wieder im aktuellen Puffer
 
Und hier nun der Code:
 
 
    class XmlUtility
    {
        #region XmlOpeningTag class
        class XmlOpeningTag
        {
            
            public XmlOpeningTag(string name, string fullOpeningTag)
            {
                TagName = name;
                FullOpeningTag = fullOpeningTag;
            }
            public string TagName { get; set; }
            public string FullOpeningTag { get; set; }
            public int ClosingTagSize
            {
                get 
                {
                    return TagName.Length + 3;
                }
            }
        }
        #endregion
        
        /// <summary>
        /// splits the given xml file into smaller parts
        /// </summary>
        /// <param name="pathToXmlFile">the path to the xml file, that is to be splitted</param>
        /// <param name="maxSizeInBytes">app. maximum size of the resulting parts</param>
        /// <param name="resultPath">where to write (path) the resulting files</param>
        /// <returns>list of filepaths, that were created</returns>
        public static List<string> Split(string pathToXmlFile, int maxSizeInBytes, string resultPath=null)
        {
            if (resultPath == null)
                resultPath=System.IO.Path.GetDirectoryName(pathToXmlFile);
            List<string> res = new List<string>();
            // initialize variables
            int currentFileNumber = 1;
            int sizeOfClosingTags = 0;
            string fileWithoutExtension = System.IO.Path.Combine(
                resultPath,               
                System.IO.Path.GetFileNameWithoutExtension(pathToXmlFile));
            StringBuilder output = new StringBuilder();
            Stack<XmlOpeningTag> _openElements = new Stack<XmlOpeningTag>();

            // open the xml file within a xmlreader
            System.Xml.XmlTextReader xmlReader = new System.Xml.XmlTextReader(pathToXmlFile);
            // read from the file, element by element
            while (xmlReader.Read())
            {
                string currentXmlLine = string.Empty;
                XmlOpeningTag newOpeningTag = null;
                switch (xmlReader.NodeType)
                {
                    case XmlNodeType.Element: //its an xml tag element (opening)
                        string xmlElementName = xmlReader.Name;
                        // start with the tag
                        currentXmlLine = "<" + xmlElementName;
                        bool isEmptyElement = xmlReader.IsEmptyElement;
                        // add all attributes
                        while (xmlReader.MoveToNextAttribute()) 
                            currentXmlLine += " " + xmlReader.Name + "='" + xmlReader.Value + "'";
                        // if the element is empty, close it in line
                        if (isEmptyElement )
                            currentXmlLine += "/>";
                        //otherwise just close the tag
                        else
                        {
                            currentXmlLine += ">";
                            newOpeningTag =
                             new XmlOpeningTag(
                                    xmlElementName,
                                    currentXmlLine
                                    );
                            sizeOfClosingTags += newOpeningTag.ClosingTagSize;
                        }
                        break;
                    case XmlNodeType.Text: // text node
                        currentXmlLine = xmlReader.Value;
                        break;
                    case XmlNodeType.EndElement: //end of element tag
                       // pop the last opening tag
                       XmlOpeningTag openingTag = _openElements.Pop();
                       sizeOfClosingTags -= openingTag.ClosingTagSize;
                       currentXmlLine = "</" + xmlReader.Name + ">";
                       break;
                }
                
                // lets see, if we can add this new line
                if (output.Length + sizeOfClosingTags + currentXmlLine.Length >= maxSizeInBytes
                    && xmlReader.NodeType != XmlNodeType.EndElement 
                    // endelements will be added anyways, we cant close the xml 
                    // before adding the endelement
                    )
                {
                    // na we cannot
                    // close all open tags
                    Stack<XmlOpeningTag> helperStack = new Stack<XmlOpeningTag>();
                    while (_openElements.Count > 0)
                    {
                        XmlOpeningTag ol = _openElements.Pop();
                        output.Append("</" + ol.TagName + ">");
                        helperStack.Push(ol);
                    }
                    
                    string filePathToSave = fileWithoutExtension + "." + currentFileNumber + ".xml";
                    res.Add(filePathToSave);
                    System.IO.File.WriteAllText(filePathToSave, output.ToString());
                    currentFileNumber++;
                    // clear the string builder, the old stuff was saved
                    output.Clear();
                    // open the "old open" tags in the new output
                    while (helperStack.Count > 0)
                    {
                        XmlOpeningTag ol = helperStack.Pop();
                        output.Append(ol.FullOpeningTag);
                        _openElements.Push(ol);
                    }

                }

                if (newOpeningTag != null)
                    _openElements.Push(newOpeningTag);
                output.Append(currentXmlLine);
            }
            if (output.Length > 0)
            {
                string filePathToSave = fileWithoutExtension + "." + currentFileNumber + ".xml";
                res.Add(filePathToSave);
                System.IO.File.WriteAllText(filePathToSave, output.ToString());
                currentFileNumber++;
            }
            
            xmlReader.Close();
            return res;
        }
    }