TwinCat Load Symbol Data @camper


  • Mod

    booster schrieb:

    Nein, es werden schlicht keine Information dazu geliefert.

    Das ist ja genau die Frage. Wo werden keine Informationen mehr geliefert.
    Über die Ads c++ Schnittstelle?
    Darum die Frage was liefert die .Net Schnittstelle?

    Ah, jetzt verstehe ich die Frage. Sorry, war etwas verwirrt.
    ...
    testing
    ...
    Nope, die gesuchte Variable ist wirklich nicht da 😞

    booster schrieb:

    Und woher weisst du dass du die richtigen Informationen (und nicht nur irgendwelche zufälligen Daten) erhalten hast?

    Na weil ich mir ja anschauen und vergleichem kann was in TwinCat definiert ist und das nachher über die Schnittstelle geliefert wird. Und das hat bisher gepasst.

    Aha. Und du bist natürlich in der Lage, jeden möglichen Fall vorher zu testen und nicht verantwortlich, wenn später etwas schief geht, weil du etwas nicht bedacht hast. Weil nicht sein kann, was nicht sein darf 😉 Womit ich nicht sagen will, dass man Fehler komplett ausschliessen kann. Andereseits ersetzt aber Testen nicht eigenes Denken.

    booster schrieb:

    Das geht nur auf einem Weg: indem die Implemention mit der Spezifikation abgleichst
    

    Welche Implementation. Die von Beckhoff?[/code]

    Nein. Der Code deines Programmes. Was antwortest du, wenn dein Chef fragt, ob der Code funktioniert?

    Das ist eine inkompatible Änderung des Protokolls, weil hier die Interpretation von Datenfeldern, die bereits eine wohldefinierte Bedeutung haben, geändert werden muss.

    booster schrieb:

    Ich versteh nur Bahnhoff.

    ref_property<datatype_entry_prop::hasBitValues, flag< AdsDatatypeEntry, datatype_entry_prop::flags, ADSDATATYPEFLAG_BITVALUES>>,

    Das ist die einzige Stelle bei der du das Symbol verwendest. Und ehrlich gesagt habe ich Null Ahnung was du da überhaupt machst.

    Das ist deshalb die einzige Stelle, weil ich das Flag noch nicht benutze und also dessen Folgen noch nicht berücksichtigt werden. Bin ja erst 90% fertig.

    Der Code selbst ist rein deklarativ:
    die Zeile ist ja Teil einer Argumentliste und fügt die Spezialisierung von ref_property dieser Liste hinzu.
    ref_property ist ein paar aus einem int und einem Typ. Der Typ (flag<...>) enthält all den Code, der benötigt wird, um auf dieses Flag innerhalb der binären Daten zuzugreifen. structured_type ist nichts weiter als eine Aufzählung dieser int-Type-Paare und stellt praktisch eine map dar, die zur Compilezeit eine Zuordnung des ints zu diesem Typ ermöglicht (in properties_of<T>::get_accessor).

    Wozu das Ganze? Ich wollte eben nicht 1000 mal den gleichen Code schreiben. Denn wie auf einzelne Felder der Binärstruktur zuzugreifen ist, sieht ja immer mehr oder weniger gleich aus. Gerade wenn subtile Unterschiede bestehen, übersieht man diese aber leicht.
    Was ich also mache, ist erst mal abstrakt zu beschreiben, welche Art von Datenfeldern existieren, und wie auf diese zuzugreifen ist; das sind die verschiedenen Formen von Accessoren. Und im Anschluss wird in den Spezialisierungen von properties_of Zeile für Zeile, jedes einzelne Datenfeld aufgezählt und der dafür passende Accessor spezifiziert.
    Diese Zuordnung funktioniert nahezu 1:1, so dass man eben die Übereinstimmung mit der Spezifikation leicht überprüfen kann, sofern man sich außerdem überzeugt hat, dass
    1. die Accessoren das tun, was sie sollen, und
    2. auch der richtige Accessor ausgewählt wird.

    Die konkrete Zeile besagt letzlich, dass dem int Wert des enums dataype_entry_prop::hasBitValues der ein flag-Accessor zugeordnet ist, dieser Accessor hängt selbst von einer anderen Property (auf die per datatype_entry_prop::flags verwiesen wird) ab, verknüpft diesen Wert binär mit der angegebenen Maske (der 3.Templateparameter) und vergleicht das Ergebnis mit der Maske.

    Ohne diese Strukturen, hätte ich dem Template ref (oder alternativ entsprechend vielen normalen Klassen) entsprechend viele Zugriffsfunktionen spendieren müssen, gleichzeitig würde der Code zwischen diesen Funktionen zum Teil subtil varrieren, weil semantisch gleichartige Felder nicht immer denselben Namen haben oder an der gleichen Stelle in der Struktur gefunden werden. Das kannst du im Ansatz bereits in den adsDT und adsSym-Klassen sehen in dem Code, den ich ganz am Anfang gebracht hatte. Mit jedem weiteren Datenstruktur, die das Programm kennen soll, verschlimmert sich die Situation.
    Es ist wahrscheinlich auch nicht so, dass all die extra Information die du jetzt bekommen kannst, für dich nutzlos ist.
    Structured Text erlaubt ja beispielsweise wertbeschränkte Variablen:

    VAR
        myInt: INT (1..10);
    END_VAR
    

    Unabhängig davon, was dein Programm macht, ob bloß Überwachung oder auch Änderungen von Speicher, wirst du wahrscheinlich diese Beschränkungen beachten wollen. Die Information über diese Wertgrenzen wird aber durch Attribute gesendet, mit dem einfachen Beispielcode bekommst du sie nie zu sehen.


  • Mod

    Wenn ich im Menü auf TwinCat/Activate Configuration gehe, kommt die Meldung

    Device 'Gerät 1 (EtherCAT)' needs sync master (at least one variable linked to a task variable).

    Das war vorher nicht der Fall und ist evtl. die Ursache für mein Problem. Wie behebe ich das?



  • Die Meldung kenne ich wenn statt einem TwinCAT XAE Project (XML format) nur ein TwinCAT PLC Projekt angelegt wurde.

    Aber das hast du ja nicht wie du schon gesagt hast.
    Ich bin nicht der PLC Programmierer und unser Experte hier ist unterwegs.
    Drum kann ich nicht viel dazu sagen. Vieleicht einfach nochmals ein neues Projekt anlegen


  • Mod

    Neue VM aufgesetzt und geht wieder. Snapshot gemacht 😃


  • Mod

    Anleitung zum Dev-Maschine kaputtmachen:

    PROGRAM MAIN
    VAR
    	myInt : INT := 10;
    	aufzaehlung : (eins, zwei, drei);
    END_VAR
    

    Das ist absolut gültiger Code. Der Compiler ist damit glücklich und min Programm kann die Aufzählung super auslesen. Aber: sei nicht eingelogt, und wechsle zu tabular view: erfreue dich an einer sehr sehr langen Fehlermeldung in einer Messagebox und einem Absturz+Neustartaufforderung von Visual Studio.

    2. Variante: (die ich bisher nur durch zurückgehen auf einen früheren Snapshot lösen konnte, du bist gewarnt!)

    TYPE
    
    END_TYPE
    PROGRAM MAIN
    VAR
    	myInt : INT := 10;
    END_VAR
    

    Das ist nat. kein legaler Code, der Compiler stört sich daran aber nicht und erzeugt gerne ein Programm, in dem main fehlt (wie zuvor beschrieben). Die Situation bleibt so, selbst wenn der fehlerhafte type-Block entfernt wird. Windowsneustart, Anlegen eines neuen Projektes etc. helfen nicht.
    Ist das wirklich Software, die im Produktiveinsatz ist und mit Geld verdient wird? Wobei ich verstehen kann, dass der Support Geld einbringt...



  • Variante 1 ist kein legaler code. Keine Typdefinition in einem POU. Dafür gibt es die DUTs. Dass die Variante kompiliert wußte ich allerdings nicht. Da hätte ich auch einen Fehler erwartet.

    Aber Variante ist bis auf den genannten fehlerhaften Type-block eigentlich gültiger Code. Main darf leer sein.

    Ist das wirklich Software, die im Produktiveinsatz ist und mit Geld verdient wird?

    Ja 🙂

    TwinCat basiert auf codesys -> https://www.codesys.com/

    Was da Beckhoff noch alles dazu erfunden hat weiß ich nicht.
    Wobei der Support von Beckhoff kostenlos ist wenn man Kunde dort ist (Hardware)



  • Jetzt nicht den Überblick verlieren.

    Das geht nur auf einem Weg: indem die Implemention mit der Spezifikation abgleichst

    Ich hatte den Satz nicht richtig verstanden.
    Mann muss die seine Implementation mit der Spezifikation (von Beckhoff) abstimmen?
    Na klar. Ich programmiere ja nach der Spezifikation.
    Aber woher weißt du dass die Spezifikation im Ordner 3.1\sdk gilt.
    Wie gesagt laut Beckhoff gilt für jedes Modul eine andere Spezifikation.

    2. Es ist interessant welche Informationen du daraus rausziehen kannst. Auch wenn ich den Code nicht wirkliche verstehe. Aber ich benötige die Menge der Informationen auch gar nicht. Mir ist egal ob ein enum ein flag-Accessor zugeordnet ist oder nicht.
    Ich beobachte den Speicher auch nicht oder verändere ihn.
    Was ich benötige ist die Informationen der Variablen:

    Name
    IndexGroup
    IndexOffset
    Datatype
    Size

    mehr nicht

    damit ich mit AdsSyncReadWriteReq geziehlt die Variable beschreiben kann.

    Es ist toll und beeindruckend wie du dich in c++ auskennst aber aktuell komme ich damit meiner Lösung nicht näher.

    3. Ich habe deinen Code jetzt nochmals kopiert und in ein neues Visual Studio Projekt (VS 2017) eingefügt. Da ich es in meinem Projekt nicht zum laufen bekomme, da ich dort an verschiedenen Stellen die TcAdsDef.h und TcAdsAPI.h eingebunden habe.

    In dem neuen Projekt habe ich den Code erst mal komplett unverändert (bis auf #include "stdafx.h" -> Code um 1 Zeile verschoben) versucht zu kompilieren. Erhalte hier aber noch 4 Fehler:

    Error C3861 'exists': identifier not found adsdata.cpp 323
    Error C3861 'value': identifier not found adsdata.cpp 323
    Error C2664 'void std::vector<adsData::attributeInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 993
    Error C2664 'void std::vector<adsData::enumInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 999


  • Mod

    booster schrieb:

    Erhalte hier aber noch 4 Fehler:

    Error C3861 'exists': identifier not found adsdata.cpp 323
    Error C3861 'value': identifier not found adsdata.cpp 323
    Error C2664 'void std::vector<adsData::attributeInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 993
    Error C2664 'void std::vector<adsData::enumInfo,std::allocator<_Ty>>::reserve(const unsigned int)': cannot convert argument 1 from 'void' to 'const unsigned int' adsdata.cpp 999

    Stimmt. Komisch dass das bisher kompiliert hat.
    Die Zeile muss

    return optional<T, flag, Access>::exists(p, q) ? optional<T, flag, Access>::value(p, q) : default_value;
    

    lauten.



  • dann kommen die nächsten zwei Fehler

    Error	C3861	'get': identifier not found	AdsSymbolLoader	adsdata.cpp	259	
    Error	C2228	left of '.find' must have class/struct/union	adsdata.cpp	259
    

  • Mod

    Selbes Prinzip:

    ASSERT_THROW((var_data<T, data, length>::get(p, q).find('\0') == std::string_view::npos)); // no embedded \0
    

    ausserdem Zeile 196

    ASSERT_THROW((next_data<T, before, align>::get(p, q) == properties_of<T>::template get<expected>(p, q)));
    

    Richtiges 2-Phasen-Lookup ist bei Visual C++ leider seit Jahren immer noch WIP, da hängt es von der Mondphase ab, ob der Fehler gefunden wird.

    Ich habe noch ein paar andere Bugs gefixed, ausserdem adsData Konstruktoren spendiert wie besprochen.
    adsdata.h
    adsdata.cpp



  • Ist nett das du dir so viel Zeit nimmst und auch die Fehler verbesserst.
    Aber wie gesagt dein Code - und mag er noch so viel können und so effizient wie möglich sein - bringt mir in meinem Projekt nicht sehr viel.

    1. Ich muss die Headerdatein verwenden die mir Beckhoff als öffentlich und verbindlich bereit stellt.

    2. Ich muss den Code warten können.

    3. Ich benötige nur die von mir angegebenen Informationen

    Name
    IndexGroup
    IndexOffset
    Size
    Datatype
    Datatype ID

    und die Informationen für die Subvariablen.

    Ich muss nicht prüfen ob das SPS Programm valide ist oder sonstiges Zeugs. Sorry.



  • Zeile 198

    ASSERT_THROW(get(p, q) <= properties_of<T>::template get<expected>(p, q));
    

    wurde in deinem neusten Code aber noch nicht gefixed

    Ändere ich das nun aber in das:

    ASSERT_THROW(next_data<T, before, align>::get(p, q) == properties_of<T>::template get<expected>(p, q));
    

    Kommt der Fehler:

    Error	C2760	syntax error: unexpected token ')', expected 'id-expression'adsdata.cpp	198
    


  • Hi camper. Ich komm da nicht weiter was will mir denn der Compilerfehler sagen?


  • Mod

    Leider hat sich mein Computer kurz vor Ostern in die ewigen Jagdgründe verabschiedet, brauchte deshalb ein bisschen Zeit.

    Aus meiner Sicht fertige Version:
    - statische Variablen in Funktionsblöcken werden richtig behandelt
    - einzelne Bits in Strukturen werden unterstützt
    - die Auswertung von Attributen und Enums habe ich entfernt, da ausdrücklich kein Bedarf besteht
    - der Bequemlichkeit wegen benutze ich Boost.Format und Boost.Preprozessor (Boost verwendest du ja sowieso schon)
    - Bezeichner einigermaßen konsistent an CamelCase angepasst
    - der Parsercode ist nicht mehr abhängig von irgenwelchen Ads- oder Windows-Headern
    - compiliert auch mit gcc7.3.0 und clang6
    - sollte jetzt auch auf BigEndian-Systemen funktionieren

    Den Code, der die serialisierten Daten liest, habe ich in einen extra Header verschoben:
    adsDataCursor.h
    wenn ADSDATACURSOR_DEBUG definiert ist, enthalten einzelne AdsDataCursor-Objekte (war zuvor ref<...>) eine Kopie der gescannten Daten, die im Debugger direkt angeschaut werden können - das ist allerdings nicht auf Effizienz getrimmt und daher sehr langsam und sollte im Allgemeinen ausgeschaltet bleiben.
    Die Fehlerbehandlung in ADSDATACURSOR_VERIFY kann durch Definition eigener Handler (ADSDATACURSOR_VERIFY_MSG_HANDLER bzw. ADSDATACURSOR_VERIFY_DEBUG_MSG_HANDLER) vor Einbinden des Headers beeinflusst werden (um z.B. eine MessageBox anzuzeigen oder eine TRACE-Meldung abzusetzen).

    Der Rest:
    adsData.h
    adsData.cpp

    Testprogramm geht alle Symbole durch, testet, ob der []-Operator funktioniert und vergleicht die Daten mit einem Aufruf per ADSIGRP_SYM_INFOBYNAMEEX
    main.cpp
    Die Implementation von ADSIGRP_SYM_INFOBYNAMEEX scheint dabei einen Bug zu haben: für Zeigervariablen, die Teil einer Struktur sind, wird die DatatypeId des Pointees geliefert, statt der des Zeigers.

    formatiert mit clang-format
    .clang-format

    PLC-Testprojekt mit verschiedenen Deklarationen
    test1.xml

    Der Compilerfehler, mit dem du zuvor gerungen hast, kann durch ein extra Paar Klammern beseitigt werden. Präprozessor-Argumente werden durch Komma getrennt, sofern sich sie nicht innerhalb (zusätzlicher) runder Klammern befinden. Für C genügt das in der Regel, in C++ treten Kommas oft auch zwischen spitzen <> und geschweiften {} Klammern auf, ohne zusätzliches rundes Klammerpaar führt das wie hier schnell dazu, dass ein einzelner Bezeicher in mehrere Makroargumente aufgeteilt wird.



  • Hi camper.

    Habe dich schon vermisst 😃
    Vielen Dank dass du dich nochmal reingehängt hast.
    Zum Code: Ich bin begesitert.

    und sogar die Klammern in einer eigenen Zeile. 👍 😃

    Die Frage soll jetzt nicht unverschämt sein. Nur aus Interesse: War dir "langweilig". Oder woran arbeitest du dass du Zeit um dich in solche Themen einzuarbeiten.


  • Mod

    booster schrieb:

    War dir "langweilig".

    Anfangs, ja. Und wenn ich es einmal anfange, sollte es auch zu einem gewissen Abschluss gebracht werden.

    Kleine Korrektur:
    AdsVarData benötigt noch eine Überladung des -> Operators

    AdsVarData* AdsVarData::operator->() noexcept { return this; }
    

    damit der -> Operator für die in adsData.h definierten Iteratoren funktionieren kann. Wird im Testcode zwar nicht gebraucht, aber der Standard verlangt es.



  • Anfangs, ja. Und wenn ich es einmal anfange, sollte es auch zu einem gewissen Abschluss gebracht werden.

    Kann ich verstehen.
    Auch wenn mich es noch interessiert hätte was du sonst so machts. Kann ich es auch verstehen wenn du das hier nicht sagen willst.

    Ich habe nun mal deinen Code auf unser PLC Projekt losgelassen.

    Und erhalte jetzt die folgende Meldung

    __thiscall AdsData::ArrayInfo::ArrayInfo(const class adsDataCursor::AdsDataCurso
    r<4,0,0> &)
    adsdata.cpp 143:
    assertion failed:
    info.elements != 0 && rBound >= info.lBound && arraySize % info.elements == 0
    array info corrupted with
    info.elements == 10
    info.lBound == 1
    rBound == 10
    arraySize == 8
    

    Jetzt habe ich noch nicht ganz verstanden was du da überprüfst und auch nicht welches Array aus meiner SPS nun "corrupted" ist. Dazu fehl mir der Name der Array Variablen. Bin noch auf der Suche wie ich mir den mit Ausgebe. Im Konstruktor ArrayInfo wo die Überprüfung statt findet steht der nicht zur Verfügung.



  • Ok habe nun mal den Fehler lokalisiert.

    Dieser Typ (der den Fehler verursacht) hat dir in deiner PLC noch gefehlt

    arraybool_p : POINTER TO ARRAY [1..10] OF BOOL;
    

    Jetzt ist nur noch unklar wieso dein Code hier das als "corrupted" deklariert. 😃



  • auto arraySize = dt.get<DtIds::size>();
    

    liefert in diesem Fall 8 zurück
    und info.elements sind 10.

    arraySize % info.elements == 0 ist dann natürlich false;
    Wenn ich nun dahinter steigen würde was dt.getDtIds::size() genau macht.
    Also dass es die größe des Arrays liefern soll ist klar.

    Aber die Programmierung dahinter ist für mich noch weit weg von verständlich.


  • Mod

    booster schrieb:

    Ok habe nun mal den Fehler lokalisiert.

    Dieser Typ (der den Fehler verursacht) hat dir in deiner PLC noch gefehlt

    arraybool_p : POINTER TO ARRAY [1..10] OF BOOL;
    

    Jetzt ist nur noch unklar wieso dein Code hier das als "corrupted" deklariert. 😃

    Weil der bei Beckhoff veröffentlichte Beispielcode diesen Fall auch nicht korrekt behandelt. Im Unterschied zu diesem sagt mein Code aber wenigstens, dass etwas nicht stimmt, und erfindet nicht Fantasiewerte 🙂
    Zeiger auf Arrays unterliegen offenbar einem Decay ähnlich wie in C, die Arrayinformation wird dabei Teil des Zeigertyps, was dazu führt, dass der Code den Zeiger wie ein Array zu behandeln versucht. Auch der Beispielcode hat ja nur geschaut, of ( pEntry->arrayDim != 0) erfüllt ist (in CAdsParseSymbols::SubSymbolInfo).

    Korrektur besteht darin, dass Zeiger und Referenzen gleich gefiltert werden.

    Ausserdem hat TwinCAT3 einen Bug (in TwinCAT2 funktioniert es so, wie man erwarten würde), indem bei verschachtelten Arays (ARRAY OF ARRAY)
    und Arrays aus Aufzählungen, die übermittelte Größe und der Datentyp des Arrays der des Elements sind.
    Lösung besteht hier darin, dass ich die Arraygröße erst mal ignoriere und erst im Nachhinein (nachdem der Elementtyp bekannt ist) restauriere und der Datentyp explizit auf blob gesetzt wird.

    Als kleine Erweiterung habe ich den Zeigerdecay rückgängig gemacht, und AdsVarData eine deref-Funktion spendiert, mit der die Variableninformationen, auf die ein Zeiger zeigt, ermittelt werden können (siehe Beispielprogramm).

    adsData.h
    adsData.cpp

    main.cpp


Anmelden zum Antworten