-Menü

Beiträge anzeigen

Dieser Abschnitt erlaubt es Ihnen, alle Beiträge anzusehen, die von diesem Mitglied geschrieben wurden. Beachten Sie, dass Sie nur Beiträge sehen können, die in Teilen des Forums geschrieben wurden, auf die Sie aktuell Zugriff haben.

Beiträge anzeigen-Menü

Beiträge - mattsches

#1
Stimmt, daran hatte ich nicht gedacht. Dann wird dir wohl nichts anderes bleiben, als den Source Code zu kopieren und abzuändern.

Alternativ könntest du auch den analogen Sollwert noch umrechnen, bevor du ihn an die Peripherie rausschreibst (modifizierter Sollwert = Maximalwert - vom Baustein ausgegebener Wert).

EDIT: Das geht natürlich genauso wenig. Denn dann würden die Taster für Ein- und Ausschalten ja wieder getauscht. Bleibt also nur das Umschreiben.
#2
Warum verknüpfst du die Taster nicht einfach entsprechend? Also den, der dunkler machen soll, an I1 und den, mit dem es heller werden soll, an I2?
#3
Der Baustein werkelt bei mir noch ohne Probleme. Weatherbit.io bieten weiterhin einen kostenfreien Zugang an, der für den Baustein auch ausreicht (wenn bzgl. der abrufbaren Daten nichts geändert wurde). Also einfach registrieren, API Key holen und ausprobieren.

Für die PV-Prognose würde ich mir mal http://forecast.solar/ anschauen. Dort bekommst du einen tatsächlichen Ertag in kWh für den Folgetag prognostiziert. Wie gut, kann ich nicht beurteilen, habe das (noch) nicht im Einsatz. Die API liefert m. W. eine JSON-Antwort, der Weatherbit-Baustein ließe sich also als Grundlage für eine Abfrage nehmen.
#4
Update, falls jemand außer mir den Baustein im Einsatz haben sollte: Das gute Stück kam nicht mit negativen Werte und leeren Objekten ( {} )  klar, wie mir erst jetzt aufgefallen ist, wo ich ebenfalls einen Fronius-Wechselrichter damit anzapfen wollte. Anbei eine überarbeitete Version, mit der das bei mir nun funktioniert. Ursprünglich wollte ich eine Datei anhängen, doch die Funktion hier im Forum scheint abgedreht worden zu sein. Daher etwas umständlicher als Quelltext:

Deklaration:

FUNCTION_BLOCK JSON_READER
VAR CONSTANT
MAX_LEVEL : UINT := 19;
END_VAR

VAR_INPUT
RST : BOOL := TRUE; (* reset *)
WATCHDOG : TIME := t#1ms; (* watchdog time *)
START_POS : UINT := 0; (* first byte in network buffer to be processed *)
STOP_POS : UINT := 0; (* last byte in network buffer to be processed *)
BUF : NW_BUF_LONG; (* network buffer that contains the JSON string as byte stream *)
END_VAR

VAR_OUTPUT
COUNT : UINT := 0; (* number of parsed JSON elements *)
LEVEL : UINT := 0; (* nesting level of current JSON element *)
ARRAY_INDEX : ARRAY[1..MAX_LEVEL] OF INT := MAX_LEVEL(-1); (* current array index value (-1: element is not part of an array) *)
PATH : STRING(STRING_LENGTH); (* full path of current JSON element *)
ELEMENT : STRING(STRING_LENGTH); (* name of current JSON element *)
VALUE : STRING(STRING_LENGTH); (* value of current JSON element *)
NEW_DATA : BOOL := FALSE; (* new data is available (next element has been parsed *)
DONE : BOOL := FALSE; (* last element has been parsed *)
ERROR : BOOL := FALSE; (* error occured, see status for details; reset is required *)
STATUS : BYTE := 0; (* ESR compatible status output ( 0=ok,
  1=error: dot not found in path string,
  2=error: closing square bracket not found in path string,
  3=max. nesting level exceeded,
100=element has been parsed,
101=watchdog timer expired *)
END_VAR

VAR
CurrentByte : UINT; (* index of currently scanned byte in network buffer *)
ObjectEmpty : BOOL := FALSE; (* current object is still empty *)
LookForValue : BOOL := FALSE; (* colon has been found, now look for value *)
FirstByte : UINT := 0; (* first byte of string to be extracted from buffer *)
LastByte : UINT := 0; (* last byte of string to be extracted from buffer *)
ElementParsed : BOOL := FALSE; (* element has been parsed *)
PathOverflow : BOOL := FALSE; (* maximum string length exceeded for path variable *)

StringFound : INT := 0; (* position of string within another string *)
LoopCount : INT := 0; (* loop counter *)
WatchdogTimer : TON; (* watchdog timer *)

_Level : UINT := 0; (* nesting level of current JSON element *)
_Path : STRING(STRING_LENGTH); (* full path of current JSON element *)
_ArrayIndex : ARRAY[1..MAX_LEVEL] OF INT := MAX_LEVEL(-1); (* current array index value (-1: element is not part of an array) *)
_Element : STRING(STRING_LENGTH); (* name of current JSON element *)
_Value : STRING(STRING_LENGTH); (* value of current JSON element *)

pValueString : POINTER TO ARRAY[0..STRING_LENGTH] OF BYTE;
END_VAR


(*

version 1.1 11 oct 2022
programmer md
tested by md

JSON_READER allows to parse a JSON string stored in a byte array (buffer) and extract the JSON elements one after another.

The parsing is monitored by a watchdog timer such that the block does cause a delay of the PLC program of more than the
configured time. The block further automatically interrupts the parsing as soon as an element and its value have been found.
It then provides both at the CTRL structured variable for external processing and continues to search for the next element
when it is called again (next cycle).

Values are always provided as strings, conversion to numeric data types needs to be done by the calling program.

*)



Code:

(* reset/initialize *)
IF RST THEN
CurrentByte := START_POS;
ObjectEmpty := FALSE;
LookForValue := FALSE;
FirstByte := 0;
LastByte := 0;

FOR LoopCount := 1 TO MAX_LEVEL DO
_ArrayIndex[LoopCount] := -1;
END_FOR

ElementParsed := FALSE;
PathOverflow := FALSE;

_Level := 0;
_Element := '';
_Path := '';
_Value := '';

COUNT := 0;
LEVEL := 0;
ELEMENT := '';
PATH := '';
VALUE := '';
NEW_DATA := FALSE;
DONE := FALSE;
ERROR := FALSE;
STATUS := 0;
END_IF;


(* reset watchdog timer *)
WatchdogTimer(IN:=FALSE , PT:= WATCHDOG, Q=> , ET=> );

(* reset new data flag *)
NEW_DATA := FALSE;

(* loop through buffer until buffer end reached OR element has been parsed OR watchdog timer has expired *)
WHILE (CurrentByte <= STOP_POS AND NOT ElementParsed AND NOT WatchdogTimer.Q AND NOT ERROR AND NOT RST AND NOT DONE) DO

WatchdogTimer(IN:=TRUE , PT:= WATCHDOG, Q=> , ET=> ); (* call watchdog timer *)

CASE BUF[CurrentByte] OF (* evaluate current character *)

0: (* end of string *)
DONE := TRUE;

123: (* '{' object start *)
IF _Level < MAX_LEVEL THEN
_Level := _Level + 1;
ObjectEmpty := TRUE;
LookForValue := FALSE;
FirstByte := 0;
_Element := '';
_Value := '';
ELSE
ERROR := TRUE;
STATUS := 3;
END_IF

125: (* '}' object end *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF

IF _Level > 1 AND NOT PathOverflow THEN
_Element := '';
_Value := '';
_Level := _Level - 1; (* up one level *)
LookForValue := FALSE; (* next string must be a key, not a value *)
FirstByte := 0;

IF NOT ObjectEmpty THEN (* don't change path if current object is still empty, i. e. nothing has been appended to the path before *)
StringFound := FINDB(_Path, '.');
IF StringFound > 1 THEN
_Path := LEFT(_Path, StringFound - 1); (* remove last element from path string *)
ELSE (* error: dot has not been found *)
ERROR := TRUE;
STATUS := 1;
END_IF
END_IF

ObjectEmpty := FALSE;
END_IF

91: (* '[' array start *)
IF LEN(_Path) + 3 < STRING_LENGTH AND NOT PathOverflow THEN
_Path := CONCAT(_Path, '[0]'); (* append array index to path *)
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
_Element := CONCAT(_Element, '[0]'); (* append array index to path *)
_ArrayIndex[_Level] := 0; (* array index *)

93: (* ']' array end *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF

IF NOT PathOverflow THEN
StringFound := FINDB(_Path, '[');
IF StringFound > 1 THEN (* remove array index from path string *)
_Path := LEFT(_Path, StringFound - 1);
_Element := LEFT(_Element, FINDB(_Path, '[') - 1);
_Value := '';
_ArrayIndex[_Level] := -1; (* reset array index *)
ELSE (* error: opening square bracket has not been found *)
ERROR := TRUE;
STATUS := 2;
END_IF
END_IF


58: (* ':' key delimiter *)
LookForValue := TRUE;

44: (* ',' element delimiter *)
IF LookForValue AND _Value <> '' THEN (* element has been parsed *)
ElementParsed := TRUE;
EXIT; (* exit loop immediately in order to first copy the element details to the output parameters *)
END_IF

IF RIGHT(_Path, 1) = ']' THEN (* previous element is part of an array *)
IF LEN(LEFT(_Path, FINDB(_Path, '['))) + LEN(INT_TO_STRING(_ArrayIndex[_Level] + 1)) + 1 <= STRING_LENGTH THEN
_ArrayIndex[_Level] := _ArrayIndex[_Level] + 1; (* increment array index *)
_Path := LEFT(_Path, FINDB(_Path, '['));
_Path := CONCAT(_Path, INT_TO_STRING(_ArrayIndex[_Level]));
_Path := CONCAT(_Path, ']');
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
ELSE (* previous element is not part of an array *)
StringFound := FINDB(_Path, '.');
IF StringFound > 1 THEN (* remove last element from path string *)
_Path := LEFT(_Path, StringFound - 1);
ELSE (* previous element was root, empty path variable *)
_Path := '';
END_IF
_Element := '';
_Value := '';
END_IF

34: (* '"' quotation mark *)
IF FirstByte = 0 THEN
FirstByte := CurrentByte + 1;
ELSE
IF NOT LookForValue THEN (* key has been parsed *)
ObjectEmpty := FALSE;
_Element := BUFFER_TO_STRING(PT:=ADR(BUF),SIZE:=STOP_POS + 1,START:=FirstByte,STOP:=CurrentByte - 1);
IF _Path = '' THEN (* path empty, use element as root for path *)
_Path := _Element;
ELSE (* append key to path *)
IF LEN(_Path) + LEN(_Element) + 1 <= STRING_LENGTH THEN
_Path := CONCAT(_Path, '.');
_Path := CONCAT(_Path, _Element);
ELSE
_Path := 'OVERFLOW';
PathOverflow := TRUE;
END_IF
END_IF
ELSE (* value has been parsed *)
_Value := BUFFER_TO_STRING(PT:=ADR(BUF),SIZE:=STOP_POS + 1,START:=FirstByte,STOP:=CurrentByte - 1);
ElementParsed := TRUE;
END_IF
FirstByte := 0;
END_IF

45, 48..57, 46, 110, 117, 108: (* minus, numbers and dot + characters for "null" (not as string but as null value *)
IF LookForValue AND FirstByte = 0 THEN
pValueString := ADR(_Value);
pValueString^[LEN(_Value) + 1] := 0;
pValueString^[LEN(_Value)] := BUF[CurrentByte];
END_IF

END_CASE
CurrentByte := CurrentByte + 1; (* proceed to next byte *)

END_WHILE

IF ElementParsed THEN
ElementParsed := FALSE;
LookForValue := FALSE;
COUNT := COUNT + 1; (* copy output values to interface *)
LEVEL := _Level;
PATH := _Path;
ARRAY_INDEX := _ArrayIndex;
ELEMENT := _Element;
VALUE := _Value;
NEW_DATA := TRUE;
STATUS := 100;
END_IF

IF CurrentByte > STOP_POS THEN (* end of string to be parsed reached *)
DONE := TRUE;
ELSIF WatchdogTimer.Q THEN
STATUS := 101;
END_IF


(* revision history
md 10 Jul 2019 rev 1.0
original version

md 11 Oct 2022 rev 1.1
fixed negative values not being recognized
fixed empty objects breaking path handling
*)


Wie cool wäre es, das über Github machen zu können...
#5
Ich glaube eher, das Löschen hat eine andere Ursache. Von mir sind auch Anhänge verschwunden, die vorher aber lange online waren. Ich tippe eher auf ein technisches Problem mit der Forensoftware oder der Datenbank, die vermutlich dahinter liegt (MySQL o. ä.). Vielleicht musste auch aus Speicherplatzgründen gelöscht werden.

Das Forum hier läuft ja allenfalls noch auf Sparflamme. Peewit dürfte der letzte Mohikaner aus der Truppe sein, die die OSCAT Bibliotheken seinerzeit erschaffen haben. Dass das Forum überhaupt noch lebt (für den Webspace wird ja irgendwer zahlen müssen), grenzt für mich schon an ein Wunder. Aber bösen Willen seitens der Admins würde ich nicht unterstellen, eher erlahmtes Interesse - was für mich durchaus nachvollziehbar ist, wenn ich mir überlege, dass das alles in der Freizeit erbracht wurde.
#6
oscat.lib for q4Logix / Forenbereich löschen
26. April 2022, 12:42:35
Ich glaube, den Bereich hier kann man getrost löschen. Außer dem Ankündigungsthread ist seit fünf Jahren nichts passiert, und die News auf der Seite von qmd4 enden im Jahr 2017 (https://www.qmd4.com/index.php/news.html). OSCAT-Bibliotheken dürfte es für dieses System kaum geben, geschweige denn Nutzer, die sich hier darüber austauschen würden.
#7
SPS-Programmierung / Re: Dimmen per Zeit
07. Januar 2022, 16:52:17
Zitat von: Matze in 07. Januar 2022, 11:13:31
bei dem Datum sind 2 Tage drin, wie kann ich das dann verstellen?

Ich verstehe die Frage nicht ganz. Wo sind zwei Tage drin? Das Ganze ist ein Beispiel, ich schalte da eine Lampe unter bestimmten Bedingungen aus, nicht nur über die Uhrzeit. Du müsstest bei dir natürlich auf die gewünschte(n) Zeitpunkt(e) vergleichen. Und wenn diese Uhrzeit verstellbar sein soll (im Sinne von einstellbar über eine Bedienoberfläche), dann müsstest du sie natürlich auf Variablen legen.

Wie du das Licht dann tatsächlich schaltest, und welche Bausteine es dafür gibt, kannst du prima in der Doku der OSCAT_BUILDING nachlesen. Ich habe wie schon gesagt z. B. den DIMM_2 im Einsatz. Wobei die Auswahl des Bausteins natürlich davon abhängt, wie genau du denn dimmen willst. Den DIMM_2 habe ich an einer Dimmerklemme im Einsatz, die einen Analogwert als Sollwert erwartet. Für nicht dimmbare Lampen (an/aus) kannst du z. B. einen SWITCH_I nehmen.

#8
SPS-Programmierung / Re: Dimmen per Zeit
04. Januar 2022, 20:33:03
Hallo Matze,

die Uhrzeit kannst du mit dem FB "RTC" auslesen (TcUtilities.lib, kurioserweise in der Kategorie "Converting + Formatting"). Vorher muss sie aber mindestens einmal gesetzt werden, was über denselben Baustein geht. Wie das geht, ist in der Doku ganz gut beschrieben: https://infosys.beckhoff.com/index.php?content=../content/1031/tcplclibutilities/html/tcplclibutilities_rtc.htm&id=

Um auf eine Uhrzeit reagieren zu können (also das Datum außer Acht zu lassen), ist eine zyklische Überführung in eine Variable vom Typ TOD sinnvoll:

todTime := DT_TO_TOD(dtDateTime);

Die kannst du dann im Code ganz einfach gegen die entsprechenden Schaltzeiten vergleichen:


Reset:= bAwayLongTerm AND
(todTime=tod#19:34:52 + tRandomTimeOffset OR
todTime=tod#07:11:23 AND NOT Dunkelheit_TOF.Q OR
todTime>tod#07:11:23 AND Dunkelheit_FN.Q ),


Mit dem Ergebnis eines solchen Vergleichs könntest du dann zu einer bestimmten Uhrzeit z. B. den SET-Eingang eines DIMM_2 beschalten und über VAL den gewünschen Schaltpunkt vorgeben. Ich würde dabei wirklich auf "=" vergleichen, damit die Vorgabe nur als Impuls von einer Sekunde dauer erfolgt und davor und danach weiterhin manuell gedimmt werden kann.
#9
Wenn du nur Bausteine brauchst, die die erwähnten Bibliotheken nicht erfordern, dann ist es vermutlich das einfachste, die benötigten Bausteine aus der Bibliothek in dein Projekt oder in eine eigene Bibliothek zu kopieren. Nicht sehr elegant (wirklich nicht), aber vermutlich der pragmatischste Ansatz.
#10
There is no way I know of how you can get hold of the compiler output as a file. And there is no use for it, at least not for debugging the PLC code. For that, you just transfer your program into the PLC and do the debugging in the programming editors while online. Don't see how any intermediate file format would help there.

P. S. This is not OSCAT related, don't expect many responses here. I guess you're better off in the SPS forum. in which you already posted (https://www.sps-forum.de/threads/einsteig-mit-festo-cecc-s-und-codesys.106211/).
#11
Hi George, just open the library stored on your hard drive like you open a project (File -> Open).
#12
Ich kann nur mutmaßen, wofür du das Programm brauchst. Studienarbeit? Facharbeit? Für die Ausbildung? Genau danach hört es sich für mich an. Und da ist mein spontaner Impuls immer: Schau dir deine Ausbildungsunterlagen an, sprich mit Mitschülern/Kommilitonen und/oder wende dich an den Ausbilder/Professor. Aber frage nicht in einem Forum, ob dir nicht jemand das mal eben programmieren kann, um dann einfach copy & paste machen zu können. Wenn du später bei der Inbetriebnahme in der zugigen Maschinenhalle sitzt, wirst du deine Aufgabenstellung auch selbst lösen müssen.

Von alledem abgesehen: Was bewegt dich, diese Frage

a) in einem Forum zu stellen, das sich auf eine konkrete Bibliothek bezieht, die mit deiner Anwendung nichts zu tun hat und
b) dort auch noch im Bereich "Home Automation"?

Weißt du, was eine Stern-Dreieck-Schaltung ist und wann sie eingesetzt wird?
#14
In CODESYS V3.5, you need to add the library's namespace as prefix to the declaration. The namespace is defined in the Library Manager in your project. If you set it to OSCAT_BASIC, your declaration should read


VAR
  PWM_DC_0: OSCAT_BASIC.PWM_DC;
END_VAR

#15
Nachdem du die Frage hier im Forum der OSCAT.lib stellst und nicht etwa im SPS-Forum, beziehe ich meine Antwort auch darauf. Was du machen willst, ist ein HTTP_GET-Aufruf. Dazu gibt es in der OSCAT Network Lib einen geeigneten Baustein (der auch genau so heißt). Die URL wird dabei als String übergeben, den du zuvor aus einer Konstante und einem variablen Teil zusammensetzen müsstest.

Der HTTP_GET wird allerdings nicht alleinstehend eingesetzt, sondern von anderen Bausteinen (IP_CONTROL, DNS_CLIENT) flankiert. Wie man das macht, kannst du dir z. B. in den *_WEATHER-Bausteinen anschauen.

Antworten bzgl. Wago-Bibliotheken bekommst du vermutlich eher im SPS-Forum oder beim Support von Wago.