Wie VBA Scripte mit der S7 verbinden

Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,

Vielen Dank für deine Antwort.

Gleich mal eine Rückfrage in deinem Post #18 hast du geschrieben das mein Skript in die ValueSubscription gehört jetzt sagst du ins _Changed Event. Ich bin verwirrt.

Nun gut, hier mein aktueller Stand den ich habe.

Code:
using Scada.AddIn.Contracts;
using Scada.AddIn.Contracts.Variable;
using System;
using System.IO;
using System.Collections.Generic;


namespace AddInOrdner_erstellen
{
    /// <summary>
    /// Description of Project Service Extension.
    /// </summary>
    [AddInExtension("Ordner_erstellen", "Einen Ordner mit Windows 10 erstellen")]
    public class ProjectServiceExtension : IProjectServiceExtension
    {
        #region IProjectServiceExtension implementation


        private readonly string _containerName;
        private IOnlineVariableContainer _container;
        private IProject _project;
               


        public void Start(IProject context, IBehavior behavior)
        {
            // enter your code which should be executed when starting the SCADA Runtime Service


            if (_project != null || _container != null)
            {
                throw new InvalidOperationException("Cannot start a new online container again.");
            }
          
            string VariableName1 = "PDE_PraegeCode";   //Stringvariable
            string VariableName2 = "PDE_FolderName";   //Stringvariable
            string VariableName3 = "PDE_CreateFolder"; //Boolvariable
            string VariableName4 = "PDE_NoFolder";     //Boolvariable
            string VariableName5 = "PDE_FileCopy";     //Boolvariable
            string VariableName6 = "PDE_NoFile";       //Boolvariable


            _project = context;


            try
            {
                // Ensure that the container is deleted
                context.OnlineVariableContainerCollection.Delete(_containerName);


                // Create a new container
                _container = context.OnlineVariableContainerCollection.Create(_containerName);


                // Add variables
                _container.AddVariable(VariableName1); //Stringvariable
                _container.AddVariable(VariableName2); //Stringvariable
                _container.AddVariable(VariableName3); //Boolvariable
                _container.AddVariable(VariableName4); //Boolvariable
                _container.AddVariable(VariableName5); //Boolvariable
                _container.AddVariable(VariableName6); //Boolvariable


                // register Event
                _container.Changed += _container_Changed;


                // Activate OnlineContainer
                _container.Activate();


            }
            catch (Exception)
            {
                
            }


        }


        private void _container_Changed(object sender, ChangedEventArgs e)
        {
            string strNr;
            string strText;
            string strPfad;
            string strOrdner;


            // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++      
            strNr = thisProject.Variables.Item("PDE_FolderName").Value;
            strText = Strings.Format((string)strNr, "000000000000"); // String 12 für den Ordnernamen
            // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


            // Exportpfad
            strPfad = @"D:\S7_Export\AppData";     // Pfad einstellen
            strOrdner = strText;


            try
            {
                if (Directory.Exists(strPfad))
                {
                    // Try to create the directory.
                    Directory.CreateDirectory(strPfad + "\\" + strOrdner);
                }
            }
            catch (Exception)
            {
                
            }


        }


        public void Stop()
        {
            // enter your code which should be executed when stopping the SCADA Runtime Service


            if (_container == null)
            {
                throw new InvalidOperationException("Stop() cannot be called before Start()");
            }


            // All events are removed here - the container gets disabled and deleted.


              _container.Changed -= _container_Changed;
               _container.Deactivate();

            
            try
            {
                _project.OnlineVariableContainerCollection.Delete(_containerName);
                _container = null;
                _project = null;
                
            }
            catch (Exception)
            {
                
            }
            
            
        }


        #endregion
    }
}

An dieser Stelle hänge ich:


Screenshot 2021-01-03 191633.png


Die beiden in Pluszeichen eingerahmte VBA Zeilen möchte in C# um basteln.
Aber ich denke anscheinend zu kompliziert und brauche das vielleicht gar nicht.

Code:
// Add variables
                _container.AddVariable(VariableName1); //Stringvariable
                _container.AddVariable(VariableName2); //Stringvariable


Code:
// Exportpfad
            strPfad = @"D:\S7_Export\AppData";     // Pfad einstellen
            strOrdner = [COLOR=#ff0000][B]An dieser Stelle brauche ich die VariableName2 das ist der Stringwert von der SPS den ich als Ordnername nutze.[/B][/COLOR]


Ich habe mir die AddInSampleLibrary genauer angeschaut und frage mich ob ich den logger (NLog) und den Error Handler unbedingt brauche.
Wenn es die RT nicht blockiert dann ist es mir egal aber wenn ich es weglassen kann dann umso besser.

Was ist eigentlich die variableChangeReceivedAction ???
Brauche ich noch die VariableSubscription am Anfang des Skriptes wenn ich jetzt das Change Event nutze ???


Vielen Dank nochmal für deine tolle Hilfe.

Mfg Tommylik
 
Zuletzt bearbeitet:
Hallo,

Gleich mal eine Rückfrage in deinem Post #18 hast du geschrieben das mein Skript in die ValueSubscription gehört jetzt sagst du ins _Changed Event. Ich bin verwirrt.

Ignoriere das bitte ich war da noch in einem anderen Beispiel drinnen - so ist es jetzt einfacher wenn wir mit deinem Source-Code arbeiten.


Ich habe leider keine Version höher als v7.60 installiert deswegen habe ich leider die nötigen .dll Dateien bzw. Bibliotheken nicht installiert und kann mir die Objekte daher nicht genau ansehen.
Aber ich versuche dir so gut wie möglich ein paar Dinge zu zeigen:

Erst zu dem ganze einfachen Dingen:

Code:
// Add variables                _container.AddVariable("PDE_PraegeCode"); //Stringvariable
                _container.AddVariable(VariableName2); //Stringvariable
                _container.AddVariable(VariableName3); //Boolvariable
                _container.AddVariable(VariableName4); //Boolvariable
                _container.AddVariable(VariableName5); //Boolvariable
                _container.AddVariable(VariableName6); //Boolvariable

Genau wie du gesagt hast wirst du die das deklarieren der Variablennamen als String sparen können und einfach direkt bei der Funktion übergeben können.
Dafür wäre nur wichtig ob die .AddVariable-Methode wirklich einen String annimmt oder eventuell das Variablen-Objekt benötigt.



Ich habe mir die AddInSampleLibrary genauer angeschaut und frage mich ob ich den logger (NLog) und den Error Handler unbedingt brauche.
Das NLog kannst du eigentlich ganz entfernen, außer du möchtest für dich selbst ein paar Sachen mitloggen.
Den ErrorHandler kannst du ebenfalls entfernen da du sowieso die möglichen Fehler mit der try-catch Anweisung abfängst. Der würde dir vermutlich nur weitere Informationen liefern.


Was ist eigentlich die variableChangeReceivedAction ???
Brauche ich noch die VariableSubscription am Anfang des Skriptes wenn ich jetzt das Change Event nutze ???
Wie gesagt das nun bitte ignorieren ich habe mit einem anderen Sample gearbeitet als du ;).
Das benötigst du jetzt eben nicht weil du dein Event bereits in der Start-Methode abonnierst und somit das _container_Changed aufgerufen wird sobald sich eine Variable ändert.



Code:
[COLOR=#3E3E3E][FONT=Courier]strPfad = @"D:\S7_Export\AppData";     // Pfad einstellen
[/FONT][/COLOR][COLOR=#3E3E3E][FONT=Courier]            strOrdner = [/FONT][/COLOR][COLOR=#ff0000][FONT=Courier][B]An dieser Stelle brauche ich die VariableName2 das ist der Stringwert von der SPS den ich als Ordnername nutze.[/B][/FONT][/COLOR]
Ok, du hast jetzt eben bereits erfolgreich deinen Online-Container erstellt und ein Event abonniert und somit wird diese _container_Changed jetzt immer aufgerufen.
Leider habe ich, wie gesagt, die v7.60 nicht installiert und kann deswegen auf die Bibliotheken nicht zugreifen.

ABER damit du eben jetzt auf die Variablen zugreifen kannst übergibt dir das Event folgende EventArgs ChangedEventArgs - diese deklarierst du als e im Methodenaufruf.
Bedeutet du müsstest jetzt z.B: soetwas machen:

Ich würde zuerst oben noch eine interne Variable deklarieren mit deinen Variablennamen:
Code:
private readonly string _containerName;        private IOnlineVariableContainer _container;
        private IProject _project;
        string[] VariableNames = new string[] { "PDE_PraegeCode",   // ID: 0  RABI: Kurze Deklaration damit die Namen immer schön weiterverwendet werden können
                                                "PDE_FolderName",   // ID: 1
                                                "PDE_CreateFolder", // ID: 2
                                                "PDE_NoFolder",     // ID: 3
                                                "PDE_FileCopy",     // ID: 4
                                                "PDE_NoFile" };     // ID: 5


Dadurch kannst du das unten ganz schön abkürzen:
Code:
// Create a new container                _container = context.OnlineVariableContainerCollection.Create(_containerName);


                // RABI: Variablen mit einer kurzen Anweisung hinzufügen
                foreach (string n in VariableNames)
                {
                    _container.AddVariable(n);
                }

Dann würde ich dir empfehlen für alle längeren Prozeduren dir einfach unter der Stop-Methode noch deine ganzen größeren Skripte zu verpacken, hier nur ein Beispiel:
Code:
 public void Stop()
        {
        }


        public void PraegeCodeCommand
        {
            // Hier musst du dann z.B.: Dateien erzeugen usw. alles was länger dauern könnte
        }

Das wichtigste kommt jetzt und zwar musst du noch diese Libary einbinden: using System.Threading.Tasks;
Danach kannst du innerhalb deiner Events z.B.: mit Hilfe der Tasks alles Asynchron starten und ebenfalls auswählen auf welche Skripte du wirklich warten möchtest.
Ganz oben siehst du noch dass ich für die Variable strnNr dann das Objekt _project verwendet habe, damit solltest du auf alles in deinem Projekt zugreifen können.

Code:
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++              strNr = _project.Variables.Item("PDE_FolderName").Value;
            strText = String.Format(strNr, "000000000000"); // String 12 für den Ordnernamen


            switch (e.Name)
            {
                case VariableNames(0): //PDE_PraegeCode
                    Task FirstTask = Task.Run(() => PraegeCodeCommand());
                    break;
                case VariableNames(1): //PDE_FolderName
                    Task SecondTask = Task.Run(() => PraegeCodeCommand());
                    break;
                case VariableNames(2): //PDE_CreateFolder
                    // Do something
                    break;
                case VariableNames(3): //PDE_NoFolder
                    // Do something
                    break;
                case VariableNames(4): //PDE_FileCopy
                    // Do something
                    break;
                case VariableNames(5): //PDE_NoFile
                    // Do something
                    break;
                default:
                    // No match
                    break;
            }
            Task.WaitAll(FirstTask, SecondTask);
            // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,

Man Super vielen Danke für deine tolle Hilfe.

Da du kein 7.60 hast konntest du nicht prüfen und hättest es gleich gesehen.
Diese Zeile funktioniert nicht. Das Wort Variables ist rot untersrichen und Item finde ich nicht in C# mit .Value am Ende.

Code:
strNr = _project.Variables.Item("PDE_FolderName").Value;


Wenn ich dieses so benutze wie du es hier als Beispiel angegeben hast sind alle VariableNames(x) rot unterstrichen.
e.Name gibt es nicht.


Code:
switch (e.Name)
            {
                case VariableNames(0): //PDE_PraegeCode
                    Task FirstTask = Task.Run(() => PraegeCodeCommand());
                    break;
                case VariableNames(1): //PDE_FolderName
                    Task SecondTask = Task.Run(() => PraegeCodeCommand());

Nur zu deiner Info diese beiden Variablen PDE_PraegeCode und PDE_FolderName sind String Variable und enthalten eine Nr. und sind nur für die Namen der Ordner und Dateien.
Nur mit den Bool Variablen kann man die Task Triggern.

Ich komme nicht so ganz klar mit der TaskFactory. Ich habe versucht es umzusetzen so wie ich deine Erklärungen verstanden habe.

Schau mal bitte was alles falsch ist ich hoffe nicht als zuviel.
Mein Stand jetzt:

Code:
using Scada.AddIn.Contracts;
using Scada.AddIn.Contracts.Variable;
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;




namespace AddInOrdner_erstellen
{
    /// <summary>
    /// Description of Project Service Extension.
    /// </summary>
    [AddInExtension("Ordner_erstellen", "Einen Ordner mit Windows 10 erstellen")]
    public class ProjectServiceExtension : IProjectServiceExtension
    {
        #region IProjectServiceExtension implementation




        private readonly string _containerName;
        private IOnlineVariableContainer _container;
        private IProject _project;
        string[] VariableNames = new string[] { "PDE_PraegeCode",   // ID: 0  Kurze Deklaration damit die Namen immer schön weiterverwendet werden können
                                                "PDE_FolderName",   // ID: 1
                                                "PDE_CreateFolder", // ID: 2
                                                "PDE_NoFolder",     // ID: 3
                                                "PDE_FileCopy",     // ID: 4
                                                "PDE_NoFile" };     // ID: 5
        private Task[] FirstTask;
        private Task[] SecondTask;


        public void Start(IProject context, IBehavior behavior)
        {
            // enter your code which should be executed when starting the SCADA Runtime Service




            if (_project != null || _container != null)
            {
                throw new InvalidOperationException("Cannot start a new online container again.");
            }


            _project = context;


            try
            {
                // Ensure that the container is deleted
                context.OnlineVariableContainerCollection.Delete(_containerName);


                // Create a new container
                _container = context.OnlineVariableContainerCollection.Create(_containerName);




                // Add variables
                // Variablen mit einer kurzen Anweisung hinzufügen
                foreach (string n in VariableNames)
                {
                    _container.AddVariable(n);
                }


                // register Event
                _container.Changed += _container_Changed;


                // Activate OnlineContainer
                _container.Activate();


            }
            catch (Exception)
            {


            }


        }


        private void _container_Changed(object sender, ChangedEventArgs e)
        {
            
            switch (e.Variable.Name)


            {
                case "PDE_CreateFolder": //PDE_CreateFolder
                    Task FirstTask = Task.Run(() => CreateFolder());
                    break;
                case "PDE_FileCopy": //PDE_FileCopy
                    Task SecondTask = Task.Run(() => FileCopy());
                    break;
                default:
                    // No match
                    break;
            }

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
            try
            {
                Task.WaitAll(FirstTask, SecondTask); [COLOR=#ff0000][B]Hier habe ich auch noch ein Problem FirstTask und SecondTassk sind rot unterstrichen.[/B][/COLOR]
            }


            catch (Exception)
            {


            }

[COLOR=#ff0000][B]Warscheinlich ist das hier an der falschen Stelle[/B][/COLOR]

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        }




        public void Stop()
        {
            // enter your code which should be executed when stopping the SCADA Runtime Service




            if (_container == null)
            {
                throw new InvalidOperationException("Stop() cannot be called before Start()");
            }


            




            // All events are removed here - the container gets disabled and deleted.




            _container.Changed -= _container_Changed;
            _container.Deactivate();




            try
            {
                _project.OnlineVariableContainerCollection.Delete(_containerName);
                _container = null;
                _project = null;


            }
            catch (Exception)
            {


            }




        }




        public void CreateFolder()
        {
            // Hier musst du dann z.B.: Dateien erzeugen usw. alles was länger dauern könnte


            string strPfad;
            string strOrdner;


            // Exportpfad
            strPfad = @"D:\S7_Export\AppData";     // Pfad einstellen
            strOrdner = "PDE_FolderName";




            try
            {
                if (Directory.Exists(strPfad))
                {
                    // Try to create the directory.
                    Directory.CreateDirectory(strPfad + "\\" + strOrdner);
                }
            }
            catch (Exception)
            {


            }




        }


        public void FileCopy()
        {
            // Hier musst du dann z.B.: Dateien erzeugen usw. alles was länger dauern könnte
        }


        #endregion
    }
}


Was mir noch fehlt wie ich mit C# an die SPS Signale zurück schicken kann.
Das ist sehr wichtig oder ich habe ein Problem mit dem SPS Programm.

So hatte ich es mit VBA gelöst.

Code:
'SPS Trigger rücksetzen wenn Ordner erstellt wurde.
thisProject.Variables.Item("PDE_CreateFolder").Value = 0

Oder wenn eine Datei nicht existiert dann möchte ich es der SPS melden.
Code:
If oFSO.FileExists("\\TOX210R01\AppData\" & strDatei & "TOX210R01.txt") = False Then
   'File doesn't exist
thisProject.Variables.Item("PDE_NoFile").Value = 1


Vielen Dank für deine tolle Hilfe und Mühe.

Mfg Tommylik
 
Zuletzt bearbeitet:
Hallo,

zuerst kann ich dein Problem mit den VariableNames leider nicht nachvollziehen, in meinem Visual Studio funktioniert das einwandfrei auch wenn ich deinen gesamten Code bei mir reinkopiere.
Das müsstest du bitte selbst finden.

Ich konnte noch feststellen dass du die Tasks noch einmal separat deklariert hast, das ist bei meiner Definition gar nicht nötig.



Bei allen anderen Problemen würde ich dir raten du schaust dir das Object Model etwas genauer an.
Dort findest du relativ schnell wie man eine Variable deklarieren kann und diese kannst du dann zuweisen.

Siehe folgendes Interface:
https://onlinehelp.copadata.com/help/760/addin/html/Variable-IVariable.htm


Und hier siehst du eben noch das ChangedEventArgs das die Property als Variable drinnen hat:
https://onlinehelp.copadata.com/help/760/addin/html/Variable-ChangedEventArgs-Properties.htm


Im Variablen-Interface siehst du dann dass es einen Value gibt. Diesen kannst du eigentlich gleich wie in VBA setzen.
 
Hallo tommy,

ich habe heute mal kurz Zeit gefunden und habe mir das ganze nochmal angesehen.

Testen müsstest du es selbst, kann aber nicht mehr so viel falsch sein.

Btw: Nachdem du auch im C# Forum angefragt hast, hätte ich dir gleich sagen können dass dir niemand das umschreiben abnimmt ;).

Code:
using System;using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Scada.AddIn.Contracts;
using Scada.AddIn.Contracts.Variable;


namespace VariableSubscription_PDE
{
    public class TommyClass
    {
        private readonly string _containerName;
        private IOnlineVariableContainer _container;
        private IProject _project;


        private string ExportPath, FolderName, Code;
        private IVariable[] ZenonVariables = new IVariable[10];


        public TommyClass()
        {
            _containerName = "TommyOnlineContainer-" + Guid.NewGuid();
        }


        public void Start(IProject context, IEnumerable<string> variables)
        {
            _project = context;
            InitializeConfiguration();


            // Ensure that the container is deleted
            context.OnlineVariableContainerCollection.Delete(_containerName);


            // Create a new container
            _container = context.OnlineVariableContainerCollection.Create(_containerName);


            // Add variables and register Event
            try
            {
                foreach (IVariable n in ZenonVariables)
                {
                    _container.AddVariable(n.Name);
                }
                _container.Changed += Container_SingleChanged;
                _container.BulkChanged += Container_BulkChanged;
                _container.ActivateBulkMode();
                _container.Activate();
            }
            catch (Exception ex)
            {


            }
        }


        public void Stop()
        {
            // All events are removed here - the container gets disabled and deleted.
            _container.Changed -= Container_SingleChanged;
            _container.BulkChanged -= Container_BulkChanged;


            _container.Deactivate();
            try
            {
                _project.OnlineVariableContainerCollection.Delete(_containerName);
            }
            catch (Exception ex)
            {


            }


        }




        /// <summary>
        /// Here you can change the functions a little bit 
        /// </summary>
        public void InitializeConfiguration()
        {
            // define the variables (you can also use custom names if you want)
            ZenonVariables[0] = _project.VariableCollection["PDE_PraegeCode"];
            ZenonVariables[1] = _project.VariableCollection["PDE_FolderName"];
            ZenonVariables[2] = _project.VariableCollection["PDE_CreateFolder"];
            ZenonVariables[3] = _project.VariableCollection["PDE_NoFolder"];
            ZenonVariables[4] = _project.VariableCollection["PDE_FileCopy"];
            ZenonVariables[5] = _project.VariableCollection["PDE_NoFile"];


            // configure all the variables
            ExportPath = @"D:\S7_Export\AppData";
            FolderName = String.Format((string)ZenonVariables[1].GetValue(0), "############");
            Code = String.Format((string)ZenonVariables[0].GetValue(0), "###########");
        }


        public void CheckTaskExecution(string VariableName)
        {
            if (VariableName == ZenonVariables[2].Name) // CreateFolder
            {
                Task TaskCreateFolder = Task.Run(() => CreateFolder());
            }
            else if (VariableName == ZenonVariables[4].Name) //FileCopy
            {
                Task TaskFileCopy = Task.Run(() => FileCopy());
            }
        }


        public void CreateFolder()
        {      
            try
            {
                DirectoryInfo FolderInfo = Directory.CreateDirectory(ExportPath + "\\" + FolderName);
                if (FolderInfo.Exists)
                {
                    ZenonVariables[2].SetValue(0, 0); // Folder successfully created
                }
                else
                {
                    ZenonVariables[3].SetValue(0, 1); // No Folder existing
                }
            }
            catch (Exception)
            {
            }
        }


        public void FileCopy()
        {
            try
            {
                if (File.Exists($"\\\\TOX210R01\\AppData\\{Code}TOX210R01.txt"))
                {
                    ZenonVariables[5].SetValue(0, 1);
                }
                else
                {
                    File.Copy($"\\\\TOX210R01\\AppData\\{Code}TOX210R01.txt", $"D:\\S7_Export\\AppData\\{FolderName}\\");
                    ZenonVariables[4].SetValue(0, 0);
                }
            }
            catch (Exception ex)
            {
            }
        }


        private void Container_BulkChanged(object sender, BulkChangedEventArgs e)
        {
            foreach(IVariable n in e.Variables)
            {
                CheckTaskExecution(n.Name);
            }
        }


        private void Container_SingleChanged(object sender, ChangedEventArgs e)
        {
            CheckTaskExecution(e.Variable.Name);
        }
    }
}
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,

Vielen Dank für deine Hilfe und Zeit. Ja das mit dem Forum war eine blöde Idee. Da ich C# nun mal nicht kann war es ein Akt der Verzweiflung.
Du hattest mir ja bestätigt das ein Project Service Extension das beste wäre so habe ich deinen Code in ein neues Service Extension Projekt hinein kopiert.

Außer ein paar Deklarationen habe ich erstmal nichts geändert.

Um das ganze besser zu verstehen habe ich noch ein paar Fragen die sich jetzt durch die neue Strucktur ergeben haben.

Auf dem folgenden Bild siehst du das rot unterstrichende.

Screenshot 2021-01-08 100057.jpg

Das liegt daran das du den Start so programmiert hast:

Code:
  public void Start(IProject context, IEnumerable<string> variables)

Wenn man es aber so schreibt geht der Fehler weg.
Code:
public void Start(IProject context, IBehavior behavior)

Hier siehst du das variables nicht genutzt wird.
Screenshot 2021-01-08 102709.png

Für was brauche ich das? ich verstehe das mit dem Guid nicht.
Code:
_containerName = "TommyOnlineContainer-" + Guid.NewGuid();

Dann eine gravierende Frage warum hast du vom Change Event auf InitializeConfiguration gewechselt?
Brauche ich dann noch die beiden Change Events Single und Bulk?

Wie wird diese Methode aufgerufen?

Code:
 public void CheckTaskExecution(string VariableName)


Wann benutzt man das @ Zeichen und wann das $ Zeichen?
Code:
ExportPath = @"D:\S7_Export\AppData";

File.Copy($"\\\\TOX210R01\\AppData\\{strDatei}TOX210R01.txt", $"D:\\S7_Export\\AppData\\{strOrdner}\\");



Code:
using Scada.AddIn.Contracts;
using Scada.AddIn.Contracts.Variable;
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;




namespace AddInOrdner_erstellen
{
    /// <summary>
    /// Description of Project Service Extension.
    /// </summary>
    [AddInExtension("Name des AddIns", "Beschreibung des AddIns")]
    public class ProjectServiceExtension : IProjectServiceExtension
    {
        #region IProjectServiceExtension implementation




        private readonly string _containerName;
        private IOnlineVariableContainer _container;
        private IProject _project;


        private string ExportPath, strOrdner, strDatei;
        private IVariable[] ZenonVariables = new IVariable[10];


        public ProjectServiceExtension()
        {
            _containerName = "TommyOnlineContainer-" + Guid.NewGuid();
        }


        public void Start(IProject context, IEnumerable<string> variables)
        {
            // enter your code which should be executed when starting the SCADA Runtime Service


            _project = context;
            InitializeConfiguration();


            // Ensure that the container is deleted
            context.OnlineVariableContainerCollection.Delete(_containerName);


            // Create a new container
            _container = context.OnlineVariableContainerCollection.Create(_containerName);


            // Add variables and register Event
            try
            {
                foreach (IVariable n in ZenonVariables)
                {
                    _container.AddVariable(n.Name);
                }
                _container.Changed += Container_SingleChanged;
                _container.BulkChanged += Container_BulkChanged;
                _container.ActivateBulkMode();
                _container.Activate();
            }
            catch (Exception)
            {


            }


        }


        public void Stop()
        {
            // enter your code which should be executed when stopping the SCADA Runtime Service


            // All events are removed here - the container gets disabled and deleted.
            _container.Changed -= Container_SingleChanged;
            _container.BulkChanged -= Container_BulkChanged;


            _container.Deactivate();
            try
            {
                _project.OnlineVariableContainerCollection.Delete(_containerName);
            }
            catch (Exception)
            {


            }


        }


        /// <summary>
        /// Here you can change the functions a little bit 
        /// </summary>
        public void InitializeConfiguration()
        {
            // define the variables (you can also use custom names if you want)
           
            ZenonVariables[0] = _project.VariableCollection["PDE_FolderName"];
            ZenonVariables[1] = _project.VariableCollection["PDE_PraegeCode"];
            ZenonVariables[2] = _project.VariableCollection["PDE_CreateFolder"];
            ZenonVariables[3] = _project.VariableCollection["PDE_NoFolder"];
            ZenonVariables[4] = _project.VariableCollection["PDE_FileCopy"];
            ZenonVariables[5] = _project.VariableCollection["PDE_NoFile"];


            // configure all the variables
            ExportPath = @"D:\S7_Export\AppData";
            // String 12 für den Ordnernamen
            strOrdner = String.Format((string)ZenonVariables[0].GetValue(0), "############");
            // String 11 für den Dateinamen         
            strDatei = String.Format((string)ZenonVariables[1].GetValue(0), "###########");
            
        }


        public void CheckTaskExecution(string VariableName)
        {
            if (VariableName == ZenonVariables[2].Name) // CreateFolder
            {
                Task TaskCreateFolder = Task.Run(() => CreateFolder());
            }
            else if (VariableName == ZenonVariables[4].Name) //FileCopy
            {
                Task TaskFileCopy = Task.Run(() => FileCopy());
            }
        }


        public void CreateFolder()
        {
            try
            {
                DirectoryInfo FolderInfo = Directory.CreateDirectory(ExportPath + "\\" + strOrdner);
                if (FolderInfo.Exists)
                {
                    ZenonVariables[2].SetValue(0, 0); // Folder successfully created
                }
                else
                {
                    ZenonVariables[3].SetValue(0, 1); // No Folder existing
                }
            }
            catch (Exception)
            {
            }
        }


        public void FileCopy()
        {
            try
            {
                if (File.Exists($"\\\\TOX210R01\\AppData\\{strDatei}TOX210R01.txt"))
                {
                    ZenonVariables[5].SetValue(0, 1);
                }
                else
                {
                    File.Copy($"\\\\TOX210R01\\AppData\\{strDatei}TOX210R01.txt", $"D:\\S7_Export\\AppData\\{strOrdner}\\");
                    ZenonVariables[4].SetValue(0, 0);
                }
            }
            catch (Exception)
            {
            }
        }




        private void Container_BulkChanged(object sender, BulkChangedEventArgs e)
        {
            foreach (IVariable n in e.Variables)
            {
                CheckTaskExecution(n.Name);
            }
        }




        private void Container_SingleChanged(object sender, ChangedEventArgs e)
        {
            CheckTaskExecution(e.Variable.Name);
        }


        #endregion
    }
}


Nochmals vielen vielen Dank für deine super Hilfe.

Mfg Tommylik
 
Hallo,

was den Fehler angeht den du erwähnt hast wird vermutlich zustande gekommen sein weil ich die neueste zenOn Version verwendet habe, dadurch ist die API etwas anders ;).
Sollte dann schon passen so wie du es ausgebessert hast.



Code:
[COLOR=#3E3E3E][FONT=Courier]_containerName = "TommyOnlineContainer-" + Guid.NewGuid();[/FONT][/COLOR]

Die GUID generierst du dir eigentlich zur zu deinem ContainerNamen dazu damit dieser ganz sicher eindeutig ist.
Das macht man normalerweise auch für COM Objekte damit sie sicher eine eindeutige ID haben. Du kannst die Funktion auch über das Visual Studio manuell ausführen.
Damit wird eine ziemlich lange "zufällige" ID erzeugt. Somit kannst du sichergehen dass der ContainerNamen nicht doppelt existiert.


Code:
[COLOR=#3E3E3E][FONT=Courier]public void CheckTaskExecution(string VariableName)[/FONT][/COLOR]

Bräuchtest du jetzt nur suchen in dem Projekt. Die Funktion wird ausgeführt wenn ein Event _Changed, _BulkChanged ausgeführt wird.
Somit brauchst du nicht bei beiden Events immer dieselben Änderungen machen sondern nur einmal gezielt in dieser Funktion.



Beim @ und $ gibt es einen einfachen Unterschied:
Code:
[COLOR=#3E3E3E][FONT=Courier]File.Copy($"\\\\TOX210R01\\AppData\\{strDatei}TOX210R01.txt", $"D:\\S7_Export\\AppData\\{strOrdner}\\");[/FONT][/COLOR]

Wenn du eben z.B.: eine Ordnerstruktur schreibst musst du ja "" verwenden. Du hast das einfach gemacht indem du vor deinem Text ein @ gesetzt hast damit wird das dann immer richtig interpretiert.
Wenn du das @ aber nicht verwendest musst du immer für dieses Sonderzeichen noch ein "" vorraussetzen. Dadurch sieht die Struktur jetzt aufgebalsen auf mit "\\". Somit gesamt nur zwei.

Wenn man ein $ Zeichen vor den Wert setzt kann man direkt im String Variablen verwenden, siehst du hier mit {strDatei}.
Wenn du das nicht verwendest müsstest du es so schreiben:

Code:
[COLOR=#3E3E3E][FONT=Courier]File.Copy($"\\\\TOX210R01\\AppData\\{0}TOX210R01.txt", $"D:\\S7_Export\\AppData\\{1}\\", strDatei, strFolder);[/FONT][/COLOR]
 
Hallo Rabi,

Vielen Dank für deine Antworten.

Ok habe ich tatsächlich nicht richtig darauf geachtet.


Und diese Methoden werden aufgerufen wenn sich eine Variable ändert??
Code:
private void Container_SingleChanged(object sender, ChangedEventArgs e)
        {
            CheckTaskExecution(e.Variable.Name);
        }


Und die ruft dann diese Methode auf??

Code:
public void CheckTaskExecution(string VariableName)


Vielen Dank nochmal für deine Antworten und ausführliche Hilfe.


Mfg Tommylik
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo,

ja genau so ist es.

Der Unterschied ist eben wichtig zu erkennen, das _SingleChanged Event liefert dir wirklich eine einzelne Variable zurück wenn sie sich ändert.

Das _BulkChanged Event liefert dir alle Variablen zurück wenn sich etwas ändert. Wirst du vermutlich gar nicht brauchen ;).


In der CheckTaskExecution werden eben die Variablennamen geprüft und anhand von denen die richtrigen Tasks gestartet damit etwas ausgeführt wird.
 
Hallo,

Aber wenn sich z. B. mehrere Variablen gleichzeitig ändern würden dann wäre es besser das _BulkChanged Event zu nutzen??

Viele Task könnte man den gleichzeitig aufrufen?? Ohne das es die RT beeinflusst??


Vielen Dank für deine Hilfe


Mfg Tommylik
 
Du müsstest versuchen wie genau der EventHandler funktioniert, leider bekomme ich vom Object Model dafür keine genauere Information.

Ich denke das Event löst nur einmal aus wenn sich mehrere Variablen in deinem OnlineContainer im gleichen Moment ändern.
Dann kannst du alle abfangen und in einem Aufruf abarbeiten.

Das normale _Changed Event wird für jede einzelne Variable aufgerufen.


Da du ja nur 2 Variablen verwendest auf die du immer reagieren musst reicht dir sicherlich der normale _SingleChanged.
Der BulkMode macht eigentlich nur Sinn wenn du wirklich sehr sehr viele Variablen hast die gleichzeitig den Wert ändern könnten.

Wie viele Tasks gleichzeitig gestartet werden können hängt von deiner Prozessorleistung und dem Vorgang innerhalb der Tasks ab.
In deinem Fall sind die Tasks ja sofort wieder beendet da keine langen Prozeduren durchlaufen werden. Eine Datei ist sofort erstellt und auch sofort kopiert.

Mit deinem Programm sollten auch mehrere 100 Tasks parallel laufen können ohne dass du etwas davon merkst.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,

Vielen Dank für deine Antwort.

Ich habe das AddIn mal ausprobiert. Es funktioniert nicht.
Ich kann aber auch nicht sagen ob das Projekt Service Extension überhaupt läuft.
Man sieht ja gar nichts.
Wie kann ich überprüfen ob das Extension mit der RT mit geladen wurde.
Wie kann ich überhaupt prüfen ob der Code funktioniert.

Ich habe einfach mal auf die Play Taste gedrückt im Visual Studio und habe in der Ausgabe folgendes bekommen:

Anmerkung 2021-01-12 135149.jpg

So wie ich das verstehe blockiert sich da was gegenseitig.


Mfg Tommylik
 
Zuletzt bearbeitet:
Hallo Rabi,

Ich habe noch nicht viel erreicht aber so viel das folgendes fehlte:

Code:
DefaultStartMode=DefaultStartupModes.Auto

Damit der Service automatisch startet.

Dann habe ich mal das ausprobiert:

Code:
            // configure all the variables
            ExportPath = @"D:\S7_Export\AppData";
            // String 12 für den Ordnernamen
            strOrdner = String.Format((string)SPSVariables[1].GetValue(0), "############");


            MessageBox.Show(strOrdner);
            MessageBox.Show(ExportPath);

Die MessageBox hat schonmal funktioniert somit ist klar das der OnlineContainer funktioniert.
Und die richtigen Variable wurden auch übergeben.

Was noch nicht funktioniert ist das der Ordner erstellt wird.
Weil der Service nicht dauerhaft läuft.

Um das zu Testen musste ich den Service manuell über einen Button starten.

Was fehlt hier noch?

Mfg Tommylik
 
Hallo,

ich habe festgestellt dass anscheinend die Start-Funktion früher aufgerufen wird als die Variablen initialisiert sind.
Dadurch läufst du relativ früh in deine Exception. Ebenfalls hab ich mit dem Foreach einen kleinen Gedankenfehler gehabt und habe dort jetzt die Objekte auf != null geprüft.

Im Anhang findest du die gesamte Extension. Das erstellen der Ordner habe ich getestet, für Testzwecke habe ich die Messageboxes noch drinnen gelassen.

Ev. kannst du damit noch etwas anfangen.
 

Anhänge

  • FinishedExtension.zip
    4,8 MB · Aufrufe: 1
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,

Super vielen Dank für deine tolle Unterstützung.

Wenn du mal Zeit hast kannst du mir das mit der VariableSubscription erklären. Wofür ist die.
Du hattest ja am Anfang schon gesagt das es mit einer Klasse am Besten wäre.
Wie Hängen diese beiden Skripte zusammen ProjectServiceExtension.cs und TommyClass.cs??
Wie ist von der Reihenfolge der Ablauf. In beiden Dateien ist eine Start Methode.

Vielen Dank nochmal für deine super Hilfe.

Mfg Tommylik
 
Hallo Rabi,

Also der Service ist zwar jetzt gestartet aber es passiert nichts wenn ich z. B. den Trigger für Ordner erstellen setze.
Dann ist mir aufgefallen das die RT sehr sehr lange brauch bis sie gestartet ist. >1 Min. Vorher war sie in ca. 20s gestartet.
Ist das so wenn AddIns mit an Board sind das die RT langsamer startet oder deutet das noch auf einen Fehler hin??

Dann wäre es gut wenn wir mal abgleichen wie Du die Variablen in der SPS nutzt und wie Du Sie in Zenon angelegt hast.
Denn du sagtest das es bei dir funktioniert bei mir leider nicht.

So sind sie bei mir angelegt.

Screenshot 2021-01-16 080006.png

Die SPS funktioniert bei mir so:

Mit einem Merker wird aus einem Typverwaltungs-DB mit 2 BLKMOV der Prägecode in 2 Strings in den DB2280 kopiert.
Und für den Ordnername noch ein L oder R extra. Deswegen String 12 für den Ordnernamen.
Nach einer kurzen ASV wird der Merker gesetzt der dann das Ordnererstellen auslösen soll.
Ist der Ordner erstellt setzt Zenon den Merker wieder zurück. Wenn nicht gibt es einen Hinweis mit PDE_NoFolder.

Vielen Dank nochmal für deine Hilfe.

Mfg Tommylik
 
Hallo,

wie gesagt du müsstest nochmal prüfen ob das Object Model von der 8.20 (die ich verwende) und der 7.60 (die du hast) ident sind.
Sonst könnte es sein dass wir bereits beim definieren von irgendwelchen Methoden in den try-catch Block laufen.

Jetzt wird es für mich leider sehr schwierig das Problem so ausfindig zu machen.
Bitte prüfe noch einmal ob deine Variablen wirklich alle auf "Extern Visible" gestellt sind unter den Externen Einstellungen jeder Variable.

Ggf. könntest du noch prüfen welche diese Eigenschaft alle haben, da zu Start der Runtime alle Variablen mit dieser Eigenschaft automatisch in die VariableCollection geschoben werden.
Das könnte eventuell deinen RT-Start verzögern, oder eben die falschen Aufrufe in deinem AddIn.


Ich würde dir dazu empfehlen dass du das Kapitel "Debugging" für die AddIns in dem Copa-Data Handbuch anschaust und prüfst wo genau das AddIn nicht mehr funktioniert:
http://download.copadata.com/filead...GERMAN/Handbuch/Programmierschnittstellen.pdf
P.S.: Du kannst auch ein VBA AddIn erstellen, wäre für dich vielleicht einfacher da du ja mit C# nicht wirklich Erfahrung hast (findest du ebenfalls beim Projekt erstellen unter SCADA).
Dort kannst du das gewohnte VBA Object Model verwenden. Auch wenn das VBA und C# Object Model fast ident sind, gibt es doch kleine aber feine Unterschiede.

Wie genau der Ablauf von deiner Steuerung ist, sollte in diesem Sinne relativ egal sein.


Zwecks der vorherigen Frage:
Die ProjectExtension ist einfach eine Klasse die vom Interface IProjectServiceExtension erbt. Würde die nicht implementiert sein würde das AddIn einfach nicht aufgerufen werden.
Innerhalb dieser Klasse wird eben dann die Klasse "TommyClass" deklariert und dementsprechend Werte zugewiesen.
Ebenfalls abonniert die TommyClass auch noch das Event "_Changed" und "_BulkChanged" von der Klasse ProjectExtension.

Hier wird es bald kompliziert das ganze zu erklären weil dir dort leider das Grundwissen fehlt was genau ein Event ist und wieso es abonniert werden muss.


Hoffe ich konnte dir trotzdem noch etwas weiterhelfen.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Rabi,


Vielen Dank für deine Antwort.


Also wenn es für dich jetzt schon schwer wird dann gute Nacht für mich.
Wie kann ich den das Object Model 7.60 mit 8.20 vergleichen ich habe ja kein 8.20?

Ok habe ich gefunden Siehe unten.


Ich habe alle Variablen auf Extern Visible gestellt keine Änderung mit dem Laden immer noch sehr langsam.
Also die Eigenschaften der Variablen sind soweit gleich bis auf den Unterschied zwischen den beiden String Variablen den vier Bool Variablen.


Das Debugging werde ich mir mal anschauen.
Also jetzt das ganze von vorne mit VB.net muss nicht sein. Vor allem gibt es da keine Beispiele ProjectServiceExtension von Copa Data.


Ich habe mal Debuggen gestartet kannst du mit diesen Fehlermeldungen etwas anfangen??


Screenshot 2021-01-18 203218.jpg


Du hast anscheinend Recht das eine Methode oder mehrere in den try-catch Block laufen.


Weil im I-Net steht das 0x80070057 darauf Hinweist das Eigenschaften oder Argumente nicht stimmen.


Ein Beispiel AddIn Beispiel für 7.60


https://onlinehelp.copadata.com/help/760/addin/html/Variable-IOnlineVariableContainer-Changed.htm


und eins für 8.20


https://onlinehelp.copadata.com/help/820/addin/html/Variable-IOnlineVariableContainer-Changed.htm


Ich werde mal versuchen das Beispiel von 7.60 umsetzen vielleicht erstmal ohne Task.


Hier gibt es auch noch ein Beispiel mit Task für die 7.60.


https://onlinehelp.copadata.com/help/760/addin/html/Variable-IOnlineVariableContainerCollection.htm




Vielen Dank nochmal für deine Hilfe.


Mfg Tommylik
 
Zuletzt bearbeitet:
Zurück
Oben