From b2948546dc477bce0fdb190719cbc51e4b307048 Mon Sep 17 00:00:00 2001 From: Barpfotenbaer Date: Thu, 2 Nov 2023 09:17:47 +0100 Subject: [PATCH] Initial commit, derived from former development (2018-2020= --- Makefile | 18 ++ README.md | 16 +- helper.go | 563 +++++++++++++++++++++++++++++++++++++++++++ snap7a.go | 16 ++ snap7b.h | 33 +++ tools.go | 32 +++ wrap7.go | 705 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1380 insertions(+), 3 deletions(-) create mode 100644 Makefile create mode 100644 helper.go create mode 100644 snap7a.go create mode 100644 snap7b.h create mode 100644 tools.go create mode 100644 wrap7.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..80d4dd4 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +# Makefile to enable snap7 functionality +all: download unzip compile #delete + +download: +# cp -r ../../../../snap7-full-1.4.2 ./ + wget -O snap7-full-1.4.2.7z https://sourceforge.net/projects/snap7/files/1.4.2/snap7-full-1.4.2.7z/download + + +unzip: + 7z -y x snap7-full-1.4.2.7z snap7-full-1.4.2 + +compile: + cd ./snap7-full-1.4.2/build/unix/ && make -f x86_64_linux.mk all + echo "$(shell pwd )/snap7-full-1.4.2/build/bin/x86_64-linux/" > /etc/ld.so.conf.d/snap7.conf && ldconfig + chown 1000:1001 snap7-full-1.4.2* -R + +delete: + rm snap7-full-1.4.2.7z diff --git a/README.md b/README.md index 84329b5..bc94e47 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,15 @@ # wrap7 -wrap7 is a Snap7 wrapper for golang. In comparison to snap7-go ( https://pkg.go.dev/github.com/avikki/snap7-go ) it is possible to implement a virtual S7-315 server. - -Licence is identical to https://snap7.sourceforge.net/licensing.html \ No newline at end of file +Go-Wrapper for Snap7 + +When using as go module it is necessary to first enter vendor directory + +"cd vendor/git.archium.org/archium_public/wrap7" + +and execute Makefile as root: + +"sudo make" + +to download and install snap7-full-1.4.2. + +Be aware to edit Makefile to your system requirements! And consider better to install Snap7 in the /usr/lib tree! diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..222bdc1 --- /dev/null +++ b/helper.go @@ -0,0 +1,563 @@ +// wrap7 project wrap7.go +package wrap7 + +import ( + "encoding/binary" + "fmt" + "math" + "regexp" + "strconv" + "strings" + "time" + "unsafe" + + "github.com/albenik/bcd" +) + +type DataTypeType string + +const ( + DTTint DataTypeType = "int" + DTTdint DataTypeType = "dint" + DTTreal DataTypeType = "real" + DTTdatetime DataTypeType = "date_and_time" + DTTdate DataTypeType = "date" + DTTtime DataTypeType = "time" + DTTbool DataTypeType = "bool" + DTTbyte DataTypeType = "byte" + DTTchar DataTypeType = "char" + DTTword DataTypeType = "word" + DTTdword DataTypeType = "dword" +) + +// Gets Wordlength of S7-Type +/* +func MeasureSPSTypeLength(s string) int8 { + switch strings.ToLower(s) { + case "int": + return 2 + case "dint": + return 4 + case "real": + return 4 + case "date_and_time": + return 8 + case "date": // Nicht verifiziert + return 2 + case "time": // Nicht verifiziert + return 4 + case "bool": + return 1 + case "char": + return 1 + case "byte": + return 1 + case "word": // Nicht verifiziert + return 2 + case "dword": // Nicht verifiziert + return 4 + default: // Nur im Fehlerfall + return -1 + } +} +*/ +func MeasureSPSTypeLength(s DataTypeType) int8 { + switch strings.ToLower(string(s)) { + case string(DTTint): + return 2 + case string(DTTdint): + return 4 + case string(DTTreal): + return 4 + case string(DTTdatetime): + return 8 + case string(DTTdate): // Nicht verifiziert + return 2 + case string(DTTtime): // Nicht verifiziert + return 4 + case string(DTTbool): + return 1 + case string(DTTchar): + return 1 + case string(DTTbyte): + return 1 + case string(DTTword): // Nicht verifiziert + return 2 + case string(DTTdword): // Nicht verifiziert + return 4 + default: // Nur im Fehlerfall + return -1 + } +} + +// Sets the bit at pos in the integer n. pos = 0 is first. +func SetBit(n int, pos uint) int { + n |= (1 << pos) + return n +} + +// Clears the bit at pos in n. pos = 0 is first. +func ClearBit(n int, pos uint) int { + mask := ^(1 << pos) + n &= mask + return n +} + +// Check if bit at pos n is set. pos = 0 is first. +func HasBit(n int, pos uint) bool { + val := n & (1 << pos) + return (val > 0) +} + +// Sets the bit at pos in the integer n. pos = 0 is first. +func Bit2Pos(n int) (pos uint) { + poss := uint(unsafe.Sizeof(^int(0)) * 8) //Number of Bytes + + for p := uint(0); p < poss; p++ { + if n == 1<

> 4) * 10)) // gos7 + return int((bcd & 0xF) + ((bcd&0xF0)>>4)*10) + +}*/ +/* +const ( + NILDATE = "1999-11-30" + NILTIME = "00:00:00 +0000 UTC" +) +*/ + +// DBufferDateNTime wird für die drei Datentypen Date_and_Time, Date und Time benötigt +func DBufferDateNTime(b *[]byte) time.Time { + var ohYear, ohMonth, ohDay, ohHour, ohMinutes, ohSeconds, ohMilliseconds int + + switch len(*b) { + default: + panic("Wrong length of byte-array in function DBufferDateNTime") + case 8: //Date und Time gesetzt + if Year := int(bcd.ToUint8((*b)[0])); Year < 90 { + ohYear = Year + 2000 + } else { + ohYear = Year + 1900 + } + //ohYear = int(int16(binary.BigEndian.Uint16((*b)[0:2]))) //int((*b)[0])*256 + int((*b)[1]) + ohMonth = int(bcd.ToUint8((*b)[1])) + ohDay = int(bcd.ToUint8((*b)[2])) + ohHour = int(bcd.ToUint8((*b)[3])) + ohMinutes = int(bcd.ToUint8((*b)[4])) + ohSeconds = int(bcd.ToUint8((*b)[5])) + ohMilliseconds = 1e6 * ((int(bcd.ToUint8((*b)[6])) * 10) + (int(bcd.ToUint8((*b)[7])) / 10)) // Besten Dank an https://github.com/robinson/gos7/blob/master/helper.go, aber Achtung!!! Dort ist die Umrechnung der Millisekunden fehlerhaft, da die Multiplikation mit 1e6 fehlt. + //fmt.Println("DATENTIME") + case 5: // Time gesetzt, Date wird auf 0000-01-01 gesetzt + ohYear = 0 + ohMonth = 1 + ohDay = 1 + ohHour = int(bcd.ToUint8((*b)[0])) + ohMinutes = int(bcd.ToUint8((*b)[1])) + ohSeconds = int(bcd.ToUint8((*b)[2])) + ohMilliseconds = 1e6 * ((int(bcd.ToUint8((*b)[3])) * 10) + (int(bcd.ToUint8((*b)[4])) / 10)) + //fmt.Println("TIME") + case 3: // Date gesetzt, Time wird auf 00:00:00.000 gesetzt + if Year := int(bcd.ToUint8((*b)[0])); Year < 90 { + ohYear = Year + 2000 + } else { + ohYear = Year + 1900 + } + //ohYear = int(int16(binary.BigEndian.Uint16((*b)[0:2]))) //int((*b)[0])*256 + int((*b)[1]) + ohMonth = int(bcd.ToUint8((*b)[1])) + ohDay = int(bcd.ToUint8((*b)[2])) + ohHour = 0 + ohMinutes = 0 + ohSeconds = 0 + ohMilliseconds = 0 // Besten Dank an https://github.com/robinson/gos7/blob/master/helper.go, aber Achtung!!! Dort ist die Umrechnung der Millisekunden fehlerhaft, da die Multiplikation mit 1e6 fehlt. + //fmt.Println("DATE") + } + + return time.Date(ohYear, time.Month(ohMonth), ohDay, ohHour, ohMinutes, ohSeconds, ohMilliseconds, time.UTC) +} +func DBufferDateNTime2(b []byte) time.Time { + var ohYear, ohMonth, ohDay, ohHour, ohMinutes, ohSeconds, ohMilliseconds int + + switch len(b) { + default: + panic("Wrong length of byte-array in function DBufferDateNTime") + case 8: //Date und Time gesetzt + if Year := int(bcd.ToUint8(b[0])); Year < 90 { + ohYear = Year + 2000 + } else { + ohYear = Year + 1900 + } + //ohYear = int(int16(binary.BigEndian.Uint16((*b)[0:2]))) //int((*b)[0])*256 + int((*b)[1]) + ohMonth = int(bcd.ToUint8(b[1])) + ohDay = int(bcd.ToUint8(b[2])) + ohHour = int(bcd.ToUint8(b[3])) + ohMinutes = int(bcd.ToUint8(b[4])) + ohSeconds = int(bcd.ToUint8(b[5])) + ohMilliseconds = 1e6 * ((int(bcd.ToUint8(b[6])) * 10) + (int(bcd.ToUint8(b[7])) / 10)) // Besten Dank an https://github.com/robinson/gos7/blob/master/helper.go, aber Achtung!!! Dort ist die Umrechnung der Millisekunden fehlerhaft, da die Multiplikation mit 1e6 fehlt. + //fmt.Println("DATENTIME") + case 5: // Time gesetzt, Date wird auf 0000-01-01 gesetzt + ohYear = 0 + ohMonth = 1 + ohDay = 1 + ohHour = int(bcd.ToUint8(b[0])) + ohMinutes = int(bcd.ToUint8(b[1])) + ohSeconds = int(bcd.ToUint8(b[2])) + ohMilliseconds = 1e6 * ((int(bcd.ToUint8(b[3])) * 10) + (int(bcd.ToUint8(b[4])) / 10)) + //fmt.Println("TIME") + case 3: // Date gesetzt, Time wird auf 00:00:00.000 gesetzt + if Year := int(bcd.ToUint8(b[0])); Year < 90 { + ohYear = Year + 2000 + } else { + ohYear = Year + 1900 + } + //ohYear = int(int16(binary.BigEndian.Uint16((*b)[0:2]))) //int((*b)[0])*256 + int((*b)[1]) + ohMonth = int(bcd.ToUint8(b[1])) + ohDay = int(bcd.ToUint8(b[2])) + ohHour = 0 + ohMinutes = 0 + ohSeconds = 0 + ohMilliseconds = 0 // Besten Dank an https://github.com/robinson/gos7/blob/master/helper.go, aber Achtung!!! Dort ist die Umrechnung der Millisekunden fehlerhaft, da die Multiplikation mit 1e6 fehlt. + //fmt.Println("DATE") + } + + return time.Date(ohYear, time.Month(ohMonth), ohDay, ohHour, ohMinutes, ohSeconds, ohMilliseconds, time.UTC) +} + +// DateNTimeDBuffer wird für die drei Datentypen Date_and_Time, Date und Time benötigt +func DateNTimeDBuffer(t *time.Time, strictZeroTime bool) (b []byte) { + + ohYear := t.Year() + ohMonth := int(t.Month()) + ohDay := t.Day() + ohHour := t.Hour() + ohMinutes := t.Minute() + ohSeconds := t.Second() + ohWeekday := int(t.Weekday()) + 1 + ohNanoseconds := t.Nanosecond() + ohMillisecondsAA := int(int64(ohNanoseconds/1e6) / 10) // msh = First two digits of miliseconds + ohMillisecondsB := int(int64(ohNanoseconds/1e6) % 10) // msl = Last digit of miliseconds + + if ohYear == 0 && ohMonth == 1 && ohDay == 1 { + b = make([]byte, 5, 5) // Nur Zeit gesetzt + //fmt.Println("Keine Jahresangabe") + b[0] = bcd.FromUint8(uint8(ohHour)) + b[1] = bcd.FromUint8(uint8(ohMinutes)) + b[2] = bcd.FromUint8(uint8(ohSeconds)) + b[3] = bcd.FromUint8(uint8(ohMillisecondsAA)) + b[4] = bcd.FromUint8(uint8(ohMillisecondsB*10 + ohWeekday)) + } else if ohHour == 0 && ohMinutes == 0 && ohSeconds == 0 && ohNanoseconds == 0 && strictZeroTime == false { //wird strictZeroTime auf true gesetzt, dann sind wird die Zeit selbst dann als Zeit interpretiert, wenn die Uhrzeit 0:0:0.000 beträgt. Das ist prinzipiell möglich und würde gegebenfalls zu einer Fehlinterpretation des Zeitstempels führen. + b = make([]byte, 3, 3) // Nur Datum gesetzt + //fmt.Println("Keine Zeitangabe") + if ohYear < 2000 { + ohYear -= 1900 + } else { + ohYear -= 2000 + } + b[0] = bcd.FromUint8(uint8(ohYear)) + b[1] = bcd.FromUint8(uint8(ohMonth)) + b[2] = bcd.FromUint8(uint8(ohDay)) + } else { // Datum und Zeit gesetzt + b = make([]byte, 8, 8) + if ohYear < 2000 { //Achtung! if y > 1999 {y -= 2000} bei https://github.com/robinson/gos7/blob/master/helper.go ist falsch, da die Subtraktion mit 1900 fehlt. + ohYear -= 1900 + } else { + ohYear -= 2000 + } + b[0] = bcd.FromUint8(uint8(ohYear)) + b[1] = bcd.FromUint8(uint8(ohMonth)) + b[2] = bcd.FromUint8(uint8(ohDay)) + b[3] = bcd.FromUint8(uint8(ohHour)) + b[4] = bcd.FromUint8(uint8(ohMinutes)) + b[5] = bcd.FromUint8(uint8(ohSeconds)) + b[6] = bcd.FromUint8(uint8(ohMillisecondsAA)) + b[7] = bcd.FromUint8(uint8(ohMillisecondsB*10 + ohWeekday)) + } + + return b +} +func DateNTimeDBuffer2(d time.Time, strictZeroTime bool) []byte { + return DateNTimeDBuffer(&d, strictZeroTime) +} +func DateNTimeDBuffer3(d time.Time, strictZeroTime bool) *[]byte { + buf := DateNTimeDBuffer(&d, strictZeroTime) + return &buf +} + +// ParseDateTime kann der Funktion DateNTimeDBuffer vorgeschaltet werden +func ParseDateTime(dts string) (time.Time, error) { + return time.Parse("DT#2006-1-2-15:04:05.000", dts) +} + +// ParseDate kann der Funktion DateNTimeDBuffer vorgeschaltet werden +func ParseDate(ds string) (time.Time, error) { + return time.Parse("D#2006-1-2", strings.ReplaceAll(strings.ToUpper(ds), "DATE", "D")) +} + +// ParseTime kann der Funktion DateNTimeDBuffer vorgeschaltet werden +func ParseTime(dt string) (time.Time, error) { + dt = strings.Replace(dt, ",", ".", 1) //TIME_OF_DAY#23:59:59,9 => TIME_OF_DAY#23:59:59.9 + dt = strings.Replace(dt, "TIME_OF_DAY", "T", 1) //TIME_OF_DAY#23:59:59.9 => T#23:59:59.9 + + /* + re, err := regexp.Compile(`^(\w{1,}#)(\d{2})D_(\d{2})H_(\d{2})M_(\d{2})S_(\d{3})MS$`) + dt2 := re.FindStringSubmatch(dt) + //dt = dt2[1] + dt2[2] + ":" + dt2[3] + ":" + dt2[4] + ":" + dt2[5] + "." + dt2[6] //T#24D_20H_31M_23S_647MS => T#24:20:31:23.647 + fmt.Println(dt2, err) + */ + dt = strings.Replace(dt, "MS", "", 1) //T#24D_20H_31M_23S_647MS => T#24D_20H_31M_23S_647 + dt = strings.Replace(dt, "D_", ":", 1) //T#24D_20H_31M_23S_647 => T#24:20H_31M_23S_647 + dt = strings.Replace(dt, "H_", ":", 1) //T#24:20H_31M_23S_647 => T#24:20:31M_23S_647 + dt = strings.Replace(dt, "M_", ":", 1) //T#24:20:31M_23S_647 => T#24:20:31:23S_647 + dt = strings.Replace(dt, "S_", ".", 1) //T#24:20:31:23S_647 => T#24:20:31:23.647 + + dt = strings.Replace(dt, "TOD", "T", 1) //TOD#23:59:59.999 => T#23:59:59.999 + + // Sorge dafür, daß am Ende immer ein Punkt, gefolgt von drei Stellen für die Millisekunden steht, da sonst der Parser ein falsches Format hat + if missingZeroes := 4 - (len(dt) - strings.Index(dt, ".")); missingZeroes > 0 { + dt += strings.Repeat("0", missingZeroes) + } else if missingZeroes < 0 { + dt += ".000" + } + + return time.Parse("T#15:04:05.000", dt) +} + +func ParseHexForByteWordAndDWord(hs string) (int, error) { + var a int64 + + // B#16#FF + // W#16#FFFF + // DW#16#FFFF FFFF oder DW#16#FFFFFFFF + // auch gültig: 16#89ABCDEF + + hs = strings.ToLower( //2. bitte nur in Kleinbuchstaben, das vermindert die Komplexität des Regulären Ausdrucks + strings.ReplaceAll(hs, " ", "")) //1. entferne alle Leerzeichen + + re, err := regexp.Compile(`^((b|w|dw)#)?(((16#)([0-9a-f]{1,8})|([0-1]{1,8})){1})$`) + + d := re.FindStringSubmatch(hs) + + if d[6] != "" { + a, _ = strconv.ParseInt(d[6], 16, 64) + } else if d[7] != "" { + a, _ = strconv.ParseInt(d[7], 2, 64) + } /*else { + panic("I should not happen") + }*/ + //fmt.Println(a, d[6], d[7]) + + return int(a), err +} + +func BfDBList(MyClient S7Object, startDB, endDB int) []int { //Bf = brute force, max-value enforced to envoid infinite loop + var dblist []int + for db := startDB; db <= endDB; db++ { + status, _ := DBRead(MyClient, db, 0, 1) + if status == 0 { //Valid DB + dblist = append(dblist, db) + } + } + return dblist +} + +func BfDBgetSize(MyClient S7Object, dbnr int) int { + var interval = 1000 + var lastvalidoffset = -1 + + for offset := 0; offset != lastvalidoffset; offset = offset + interval { + status, _ := DBRead(MyClient, dbnr, offset, interval) + if status == 0 { + lastvalidoffset = offset + } else { + //fmt.Println(offset, lastvalidoffset) + offset = lastvalidoffset + interval = interval / 2 + } + } + + return (lastvalidoffset + 1) // Achtung! +1 !! +} + +func BfDBInfo(MyClient S7Object, maxdbnr int) { + dblist := BfDBList(MyClient, 1, maxdbnr) + + for _, myDB := range dblist { + length := BfDBgetSize(MyClient, myDB) + + fmt.Print("DB ", myDB, " (", length, " Bytes)", ": ") + fmt.Println(DBRead(MyClient, myDB, 0, length)) + } + +} diff --git a/snap7a.go b/snap7a.go new file mode 100644 index 0000000..3bbd106 --- /dev/null +++ b/snap7a.go @@ -0,0 +1,16 @@ +package wrap7 + +/* +#cgo CFLAGS: -O2 -march=native +#cgo LDFLAGS: -lm -lsnap7 -L${SRCDIR}/snap7-full-1.4.2/build/bin/x86_64-linux/ +*/ +import "C" + +import ( + "github.com/gookit/color" +) + +//export logSnap7Server +func logSnap7Server(text *C.char) { + color.RGB(0x00, 0xa4, 0xb4, false).Printf("%s\n", C.GoString(text)) // Die Siemens-Farben +} diff --git a/snap7b.h b/snap7b.h new file mode 100644 index 0000000..b284718 --- /dev/null +++ b/snap7b.h @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include "./snap7-full-1.4.2/release/Wrappers/c-cpp/snap7.h" + +extern void logSnap7Server(char *); + +#ifdef OS_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include +#endif + +const int EventCallBackLength = 1024; + +S7Object Server; + +void S7API EventCallBack(void *usrPtr, PSrvEvent PEvent, int Size) +{ + + // print the event + char *text; + text = (char *)malloc(EventCallBackLength); + Srv_EventText(PEvent, text, EventCallBackLength); + logSnap7Server(text); + free(text); +}; + +void _RegisterEventCallBack(S7Object Server) +{ + Srv_SetEventsCallback(Server, EventCallBack, NULL); +} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..6836921 --- /dev/null +++ b/tools.go @@ -0,0 +1,32 @@ +// wrap7 project wrap7.go +package wrap7 + +import ( + "net" + "time" + + "github.com/go-ping/ping" +) + +const ( + PINGER_COUNT = 10 // Die Anzahl an Pings, die der Pinger zur Auswertung heranzieht. Pinger-Counts kosten richtig viel Zeit. Leider ist ein Ping alleine viel zu ungenau. + PINGER_TIMEOUT = "1000ms" // Sobald der Pinger innerhalb dieser Zeitspanne keinen Erfolg hat, liefert er 0ms Pingzeit und 100% Paketloss zur?ck. + PINGER_CRITICAL_LOSS_PERCENT = 100 +) + +func pingMyDing(myIp net.IP) (time.Duration, float64) { //Die Konversion zu net.Ip ist eigentlich überflüssig, die Limitierung des Typs an der Schnittstelle erspart aber nachgelagerte Plausibilitätsprüfungen. + pinger, err := ping.NewPinger(myIp.String()) + if err != nil { + panic(err) + } + + pinger.Count = PINGER_COUNT + pinger.Timeout, _ = time.ParseDuration(PINGER_TIMEOUT) + //pinger.Interval, _ = time.ParseDuration(PINGER_INTERVAL) + + //sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" + pinger.SetPrivileged(false) + pinger.Run() // blocks until finished + stats := pinger.Statistics() // get send/receive/rtt stats + return stats.AvgRtt, stats.PacketLoss +} diff --git a/wrap7.go b/wrap7.go new file mode 100644 index 0000000..9abf133 --- /dev/null +++ b/wrap7.go @@ -0,0 +1,705 @@ +// wrap7 project wrap7.go +package wrap7 + +/* +#cgo CFLAGS: -O2 -march=native +#cgo LDFLAGS: -lm -lsnap7 +#include +#include +#include +#include +#include +#include + + +#ifdef OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include +#endif + +#define MAXPDULENGTH 960 + +// https://support.industry.siemens.com...view/109747136 Seite 6813 +// S7-300 240 Byte +// S7-400 480 Byte +// S7-1200 240 Byte +// S7-1500 960 Byte + +int MultiRead(S7Object Client, byte (*buffer)[MAXPDULENGTH], TS7DataItem *psivar, int ItemsCount) +{ + int i; + TS7DataItem Items[ItemsCount]; + + for( i = 0; i< ItemsCount; i++ ) { + //printf("Item Nr. %d\n",i); + Items[i].Area = psivar[i].Area; + Items[i].WordLen = psivar[i].WordLen; + Items[i].DBNumber = psivar[i].DBNumber; + Items[i].Start = psivar[i].Start; + Items[i].Amount = psivar[i].Amount; + Items[i].pdata = &buffer[i]; + } + + return Cli_ReadMultiVars(Client, &Items[0], ItemsCount); +} + +int MultiWrite(S7Object Client, byte (*buffer)[MAXPDULENGTH], TS7DataItem *psivar, int ItemsCount) +{ + int i; + TS7DataItem Items[ItemsCount]; + + for( i = 0; i< ItemsCount; i++ ) { + //printf("Item Nr. %d\n",i); + Items[i].Area = psivar[i].Area; + Items[i].WordLen = psivar[i].WordLen; + Items[i].DBNumber = psivar[i].DBNumber; + Items[i].Start = psivar[i].Start; + Items[i].Amount = psivar[i].Amount; + Items[i].pdata = &buffer[i]; + } + + return Cli_WriteMultiVars(Client, &Items[0], ItemsCount); +} +*/ +import ( + "C" +) +import ( + "fmt" + "log" + "net" + "unsafe" +) + +type ( // Type-Alias ist ansich nur Kosmetik + S7Object = C.S7Object +) + +type ConditionType string + +const ( + CONDITION_RUNNING ConditionType = "running" + CONDITION_STOPPED ConditionType = "stopped" + CONDITION_UNKNOWN ConditionType = "unknown" +) + +var ( // Damit im Go-Code nicht das C-Package importiert werden muß + S7AreaDB = (int)(C.S7AreaDB) + S7AreaMK = (int)(C.S7AreaMK) + S7AreaPE = (int)(C.S7AreaPE) + S7AreaPA = (int)(C.S7AreaPA) + S7AreaTM = (int)(C.S7AreaTM) + S7AreaCT = (int)(C.S7AreaCT) + + S7WLBit = (int)(C.S7WLBit) + S7WLByte = (int)(C.S7WLByte) + S7WLWord = (int)(C.S7WLWord) + S7WLDWord = (int)(C.S7WLDWord) + S7WLReal = (int)(C.S7WLReal) + S7WLCounter = (int)(C.S7WLCounter) + S7WLTimer = (int)(C.S7WLTimer) +) + +var EventCallBackLength = (int)(C.EventCallBackLength) + +var ( + SrvAreaPE = (int)(C.srvAreaPE) + SrvAreaPA = (int)(C.srvAreaPA) + SrvAreaMK = (int)(C.srvAreaMK) + SrvAreaCT = (int)(C.srvAreaCT) + SrvAreaTM = (int)(C.srvAreaTM) + SrvAreaDB = (int)(C.srvAreaDB) +) + +const ( + BlockTypeOB = 0x38 + BlockTypeDB = 0x41 + BlockTypeSDB = 0x42 + BlockTypeFC = 0x43 + BlockTypeSFC = 0x44 + BlockTypeFB = 0x45 + BlockTypeSFB = 0x46 +) + +// Diese Konstante gibt es doppelt. Sie muß auch im C-Code angepaßt werden! +const MAXPDULENGTH int = 960 + +const MULTIREADWRITE_BUFFERS = 10 // Wievele Buffers und Items MultiRead und MultiWrite anlegen soll. + +//const DEVICE_IP = "192.168.0.10" + +// https://support.industry.siemens.com...view/109747136 Seite 6813 +// S7-300 240 Byte +// S7-400 480 Byte +// S7-1200 240 Byte +// S7-1500 960 Byte + +type MultiReadWriteItem struct { + Area, WordLen, DBNumber, Start, Amount int +} + +type TS7OrderCode struct { + Code string + V1, V2, V3 int +} + +type TSCpuInfo struct { + ModuleTypeName, SerialNumber, ASName, Copyright, ModuleName string +} + +type TS7CpInfo struct { + MaxPduLength, MaxConnections, MaxMpiRate, MaxBusRate int // MaxPduLengt zu MaxPduLength korrigiert +} + +type TS7BlocksList struct { + OBCount, FBCount, FCCount, SFBCount, SFCCount, DBCount, SDBCount int +} + +type TS7BlockInfo struct { + BlkType int // Block Type (OB, DB) + BlkNumber int // Block number + BlkLang int // Block Language + BlkFlags int // Block flags + MC7Size int // The real size in bytes + LoadSize int // Load memory size + LocalData int // Local data + SBBLength int // SBB Length + CheckSum int // Checksum + Version int // Block version + // Chars info + CodeDate string // Code date + IntfDate string // Interface date + Author string // Author + Family string // Family + Header string // Header +} + +func GetOrderCode(client C.S7Object) (int, TS7OrderCode) { + var info C.TS7OrderCode + + res := C.Cli_GetOrderCode(client, (*C.TS7OrderCode)(unsafe.Pointer(&info))) + + return (int)(res), TS7OrderCode{ + Code: C.GoString((*C.char)(unsafe.Pointer(&info.Code[0]))), + V1: (int)(info.V1), + V2: (int)(info.V2), + V3: (int)(info.V3)} /*, func() string { + return fmt.Sprintf("Order Code: %s\n, Version: %d.%d.%d\n", C.GoString((*C.char)(unsafe.Pointer(&info.Code[0]))), (int)(info.V1), (int)(info.V2), (int)(info.V3)) + }()*/ +} + +func GetCpuInfo(client C.S7Object) (int, TSCpuInfo) { + var info C.TS7CpuInfo + + res := C.Cli_GetCpuInfo(client, (*C.TS7CpuInfo)(unsafe.Pointer(&info))) + + return (int)(res), TSCpuInfo{ + ModuleTypeName: C.GoString((*C.char)(unsafe.Pointer(&info.ModuleTypeName[0]))), + SerialNumber: C.GoString((*C.char)(unsafe.Pointer(&info.SerialNumber[0]))), + ASName: C.GoString((*C.char)(unsafe.Pointer(&info.ASName[0]))), + Copyright: C.GoString((*C.char)(unsafe.Pointer(&info.Copyright[0]))), + ModuleName: C.GoString((*C.char)(unsafe.Pointer(&info.ModuleName[0])))} +} + +func GetCpInfo(client C.S7Object) (int, TS7CpInfo) { + var info C.TS7CpInfo + + res := C.Cli_GetCpInfo(client, (*C.TS7CpInfo)(unsafe.Pointer(&info))) + + return (int)(res), TS7CpInfo{ + MaxPduLength: (int)(info.MaxPduLengt), + MaxConnections: (int)(info.MaxConnections), + MaxMpiRate: (int)(info.MaxMpiRate), + MaxBusRate: (int)(info.MaxBusRate)} +} + +func ListBlocks(client C.S7Object) (int, TS7BlocksList) { + var list C.TS7BlocksList + + res := C.Cli_ListBlocks(client, (*C.TS7BlocksList)(unsafe.Pointer(&list))) + + return (int)(res), TS7BlocksList{ + OBCount: (int)(list.OBCount), + FBCount: (int)(list.FBCount), + FCCount: (int)(list.FCCount), + SFBCount: (int)(list.SFBCount), + SFCCount: (int)(list.SFCCount), + DBCount: (int)(list.DBCount), + SDBCount: (int)(list.SDBCount)} +} + +func GetAgBlockInfo(client C.S7Object, blockType, blockNum int) (int, TS7BlockInfo) { + var info C.TS7BlockInfo + /* + blockType: + OB = 0x38 + DB = 0x41 + SDB = 0x42 + FC = 0x43 + SFC = 0x44 + FB = 0x45 + SFB =0x46 + + Wrap7BlockTypeOB = 0x38 + Wrap7BlockTypeDB = 0x41 + Wrap7BlockTypeSDB = 0x42 + Wrap7BlockTypeFC = 0x43 + Wrap7BlockTypeSFC = 0x44 + Wrap7BlockTypeFB = 0x45 + Wrap7BlockTypeSFB = 0x46 + */ + + res := C.Cli_GetAgBlockInfo(client, (C.int)(blockType), (C.int)(blockNum), (*C.TS7BlockInfo)(unsafe.Pointer(&info))) + + return (int)(res), TS7BlockInfo{ + BlkType: (int)(info.BlkType), + BlkNumber: (int)(info.BlkNumber), + BlkLang: (int)(info.BlkLang), + BlkFlags: (int)(info.BlkFlags), + MC7Size: (int)(info.MC7Size), + LoadSize: (int)(info.LoadSize), + LocalData: (int)(info.LocalData), + SBBLength: (int)(info.SBBLength), + CheckSum: (int)(info.CheckSum), + Version: (int)(info.Version), + CodeDate: C.GoString((*C.char)(unsafe.Pointer(&info.CodeDate[0]))), + IntfDate: C.GoString((*C.char)(unsafe.Pointer(&info.IntfDate[0]))), + Author: C.GoString((*C.char)(unsafe.Pointer(&info.Author[0]))), + Family: C.GoString((*C.char)(unsafe.Pointer(&info.Family[0]))), + Header: C.GoString((*C.char)(unsafe.Pointer(&info.Header[0])))} +} + +/* +int GetAgBlockInfo(int BlockType, int BlockNum, PS7BlockInfo pUsrData); +int GetPgBlockInfo(void *pBlock, PS7BlockInfo pUsrData, int Size); +int ListBlocksOfType(int BlockType, TS7BlocksOfType *pUsrData, int *ItemsCount); +*/ + +func GetPlcStatus(client C.S7Object) (int, int8, ConditionType) { + var status C.int + res := C.Cli_GetPlcStatus(client, &status) + + return (int)(res), (int8)(status), func(sc int8) ConditionType { + switch sc { + case C.S7CpuStatusRun: + return CONDITION_RUNNING + case C.S7CpuStatusStop: + return CONDITION_STOPPED + default: //C.S7CpuStatusUnknown + return CONDITION_UNKNOWN + + } + }((int8)(status)) +} + +/* +Cli_GetPlcStatus(Client, &Status); + if (Check(res, "CPU Status")) + { + switch (Status) + { + case S7CpuStatusRun: + printf(" RUN\n"); + break; + case S7CpuStatusStop: + printf(" STOP\n"); + break; + default: + printf(" UNKNOWN\n"); + break; + } + }; +} +*/ + +// MultiRead liest ein Array von Werten aus der SPS +func MultiRead(client C.S7Object, items []MultiReadWriteItem) (int, [][]byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + itemsCount := len(items) + if itemsCount > MULTIREADWRITE_BUFFERS { + panic("Error in \"func MultiRead(client C.S7Object, items []MultiReadWriteItem)\": Too many items\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + var mulitReadWriteBuffer [MULTIREADWRITE_BUFFERS][MAXPDULENGTH]byte + var cItems [MULTIREADWRITE_BUFFERS]C.TS7DataItem + for i, v := range items { + cItems[i].Area = (C.int)(v.Area) + cItems[i].WordLen = (C.int)(v.WordLen) + cItems[i].DBNumber = (C.int)(v.DBNumber) + cItems[i].Start = (C.int)(v.Start) + cItems[i].Amount = (C.int)(v.Amount) + } + + res := C.MultiRead(client, (*[MAXPDULENGTH]C.uchar)(unsafe.Pointer(&mulitReadWriteBuffer)), (*C.TS7DataItem)(unsafe.Pointer(&cItems)), (C.int)(itemsCount)) + + /*DEBUG + // Baue eine Testinfo ein + mulitReadWriteBuffer[0][2] = 'G' + fmt.Println(mulitReadWriteBuffer) + */ + + // Bereinige das von Snap7 gelieferte Ergebnis und begrenze die Ausgabe auf den tatsächlichen Inhalt + var datares [][]byte + datares = make([][]byte, itemsCount) + + for i := 0; i < itemsCount; i++ { + datares[i] = make([]byte, items[i].Amount) + copy(datares[i], mulitReadWriteBuffer[i][0:items[i].Amount]) + } + + return (int)(res), datares +} + +// MultiWrite schreibt ein Array von Werten in die SPS. Auf den ersten Blick ist ein Rücklesen der Werte in MultiWrite nun ein überflüssiger Overhead. Machen wir dies hier aber nicht, müßte in Fällen, wo mit dem veränderten Speicher weitergearbeitet werden soll, ein weiteres MultiRead folgen. Dies können wir hier einsparen, wenn wir den veränderten Speicher hier zurückgeben. Wir bleiben somit auch synchron zur MultiRead-Funktion +func MultiWrite(client C.S7Object, items []MultiReadWriteItem, buffers *[][]byte) (int, [][]byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + itemsCount := len(items) + + if itemsCount > MULTIREADWRITE_BUFFERS { + panic("Error in \"func MultiWrite(client C.S7Object, items []MultiReadWriteItem)\": Too many items\n\n") // FIXME: Standardfehlermeldung erzeugen + } else if buffersCount := len(*buffers); itemsCount != buffersCount { + panic("Error in \"func MultiWrite(client C.S7Object, items []MultiReadWriteItem)\": Number of items and number of buffers must be identical\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + var mulitReadWriteBuffer [MULTIREADWRITE_BUFFERS][MAXPDULENGTH]byte + var cItems [MULTIREADWRITE_BUFFERS]C.TS7DataItem + for i, v := range items { + if len((*buffers)[i]) < v.Amount { + panic(fmt.Sprintf("Error in \"func MultiWrite(client C.S7Object, items []MultiReadWriteItem) [Index %d]\": Amount must not be larger than buffer length!\n\n", i)) // FIXME: Standardfehlermeldung erzeugen + } + + cItems[i].Area = (C.int)(v.Area) + cItems[i].WordLen = (C.int)(v.WordLen) + cItems[i].DBNumber = (C.int)(v.DBNumber) + cItems[i].Start = (C.int)(v.Start) + cItems[i].Amount = (C.int)(v.Amount) + copy(mulitReadWriteBuffer[i][0:items[i].Amount], (*buffers)[i][0:items[i].Amount]) + } + + res := C.MultiWrite(client, (*[MAXPDULENGTH]C.uchar)(unsafe.Pointer(&mulitReadWriteBuffer)), (*C.TS7DataItem)(unsafe.Pointer(&cItems)), (C.int)(itemsCount)) + + // Bereinige das von Snap7 gelieferte Ergebnis und begrenze die Ausgabe auf den tatsächlichen Inhalt + var datares [][]byte + datares = make([][]byte, itemsCount) + + for i := 0; i < itemsCount; i++ { + datares[i] = make([]byte, items[i].Amount) + copy(datares[i], mulitReadWriteBuffer[i][0:items[i].Amount]) + } + + return (int)(res), datares +} + +func ReadArea(client C.S7Object, Area, DBNumber, Start, Amount, WordLen int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + + res := C.Cli_ReadArea(client, (C.int)(Area), (C.int)(DBNumber), (C.int)(Start), (C.int)(Amount), (C.int)(WordLen), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func WriteArea(client C.S7Object, Area, DBNumber, Start, Amount, WordLen int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func WriteArea(client C.S7Object, Area, DBNumber, Start, Amount, WordLen int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_WriteArea(client, (C.int)(Area), (C.int)(DBNumber), (C.int)(Start), (C.int)(Amount), (C.int)(WordLen), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func DBRead(client C.S7Object, DBNumber, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_DBRead(client, (C.int)(DBNumber), (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func DBWrite(client C.S7Object, DBNumber, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func DBWrite(client C.S7Object, DBNumber, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_DBWrite(client, (C.int)(DBNumber), (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func MBRead(client C.S7Object, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_MBRead(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func MBWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func MBWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_MBWrite(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func EBRead(client C.S7Object, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_EBRead(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func EBWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func EBWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_EBWrite(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func ABRead(client C.S7Object, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_ABRead(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func ABWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func ABWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_ABWrite(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func TMRead(client C.S7Object, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_TMRead(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func TMWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func TMWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_TMWrite(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func CTRead(client C.S7Object, Start, Amount int) (int, []byte) { + var cbuffer [MAXPDULENGTH]C.char + res := C.Cli_CTRead(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +func CTWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte) (int, []byte) { + defer func() { + if err := recover(); err != nil { + fmt.Printf("%v", err) + } + }() + + var cbuffer [MAXPDULENGTH]C.char + + if len(*gobuffer) < Amount { + panic("Error in \"func CTWrite(client C.S7Object, Start, Amount int, gobuffer *[]byte)\": Amount must not be larger than buffer length!\n\n") // FIXME: Standardfehlermeldung erzeugen + } + + cbuffer = *(*[MAXPDULENGTH]C.char)(C.CBytes(*gobuffer)) + + res := C.Cli_CTWrite(client, (C.int)(Start), (C.int)(Amount), unsafe.Pointer(&cbuffer)) + + return (int)(res), C.GoBytes(unsafe.Pointer(&cbuffer), (C.int)(Amount)) +} + +// CliCreate creates a SPS-client +func CliCreate() S7Object { + return C.Cli_Create() +} + +// CliConnect connects to SPS-client +func CliConnect(client S7Object, ip net.IP, rack uint8, slot uint8) { + C.Cli_ConnectTo(client, C.CString(ip.String()), (C.int)(rack), (C.int)(slot)) +} + +// FIXME: Remove me sometimes +// CreateAndConnect is a wrapper for CliDisconnect for compatibility purpose, has to be removed in a later version or wrap7 +func CreateAndConnect(ip net.IP, rack uint8, slot uint8) S7Object { + log.Println("CreateAndConnect is a wrapper for CliCreateAndConnect for compatibility purpose, has to be removed in a later version or wrap7") + return CliCreateAndConnect(ip, rack, slot) +} + +// CliCreateAndConnect creates a SPS-client and connect to SPS +func CliCreateAndConnect(ip net.IP, rack uint8, slot uint8) S7Object { + client := C.Cli_Create() + C.Cli_ConnectTo(client, C.CString(ip.String()), (C.int)(rack), (C.int)(slot)) + + return client +} + +// CliConditionalCreateAndConnect creates a SPS-client after successfull ping and connects to SPS +func CliConditionalCreateAndConnect(ip net.IP, rack uint8, slot uint8) (S7Object, error) { + _, loss := pingMyDing(ip) + var client S7Object + + if loss < PINGER_CRITICAL_LOSS_PERCENT { + client = C.Cli_Create() + C.Cli_ConnectTo(client, C.CString(ip.String()), (C.int)(rack), (C.int)(slot)) + return client, nil + } else { + return client, fmt.Errorf("Unable to ping SPS [%s, %v% loss]", ip.String(), loss) + } + +} + +// CliDisconnect disconnects SPS-client from SPS +func CliDisconnect(client S7Object) { + C.Cli_Disconnect(client) +} + +func CliDestroy(client *S7Object) { + C.Cli_Destroy(client) +} + +func CliDisconnectAndDestroy(client S7Object) { + C.Cli_Disconnect(client) + C.Cli_Destroy(&client) +} + +// FIXME: Remove me sometimes +// Disconnect is a wrapper for CliDisconnect for compatibility purpose, has to be removed in a later version or wrap7 +func Disconnect(client S7Object) { + log.Println("Disconnect is a wrapper for CliDisconnect for compatibility purpose, has to be removed in a later version or wrap7") + CliDisconnect(client) +} + +/*Server Functions*/ +func SrvCreate() S7Object { + return C.Srv_Create() +} + +func SrvStart(server *S7Object) int { + return (int)(C.Srv_Start(*server)) +} + +func SrvStop(server *S7Object) { + C.Srv_Stop(*server) +} + +func SrvDestroy(server *S7Object) { + C.Srv_Destroy(server) +} + +func SrvErrorText(errorCode int, spsServerText *rune, eventCallBackLength int) { + //C.Srv_ErrorText((C.int)(ErrorCode), (*C.char)(unsafe.Pointer(&spsServerText[0])), C.EventCallBackLength) + C.Srv_ErrorText((C.int)(errorCode), (*C.char)(unsafe.Pointer(spsServerText)), (C.int)(eventCallBackLength)) +} + +func SrvRegisterArea(server *S7Object, area int, index uint16, buffer []byte, length int) { + C.Srv_RegisterArea(*server, + (C.int)(area), // Area + (C.ushort)(index), // Nummer des Datenbausteins + //unsafe.Pointer(&(buffer[0])), // Buffer + //unsafe.Pointer(&((*buffer)[0])), + C.CBytes(buffer), + (C.int)(length)) // Die Länge ist durch die Variablendefinition bereits vorgegeben und kann hier gemessen werden. + +} + +func SrvUnregisterArea(server *S7Object, area int, index uint16) { + C.Srv_UnregisterArea(*server, (C.int)(area), (C.ushort)(index)) +} + +func RegisterEventCallBack(server *S7Object) { + C._RegisterEventCallBack(*server) +} + +func SrvLockArea(server *S7Object, area int, index uint16) { + C.Srv_LockArea(*server, + (C.int)(area), // Area + (C.ushort)(index)) // Nummer des Datenbausteins +} + +func SrvUnlockArea(server *S7Object, area int, index uint16) { + C.Srv_UnlockArea(*server, + (C.int)(area), // Area + (C.ushort)(index)) // Nummer des Datenbausteins +}