Initial commit, derived from former development (2018-2020=

This commit is contained in:
Klaus Wendel, archium GmbH 2023-11-02 09:17:47 +01:00
parent e234ebcaf9
commit b2948546dc
7 changed files with 1380 additions and 3 deletions

18
Makefile Normal file
View File

@ -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

View File

@ -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
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!

563
helper.go Normal file
View File

@ -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<<p {
return p
}
}
panic("Bit-position not found")
}
// ... and reverse
func Pos2Bit(pos uint) (n int) {
n = 1 << pos
return
}
/*
* Datenkonversion zwischen SPS und PC
*
*/
func DBufferString(b *[]byte) string {
return strings.Trim(string(*b), " ")
}
func DBufferString2(b []byte) string {
return strings.Trim(string(b), " ")
}
func StringDBuffer(s *string) []byte {
return []byte(*s)
}
func StringDBuffer2(ß string) []byte {
return StringDBuffer(&ß)
}
func StringDBuffer3(ß string) *[]byte {
buf := StringDBuffer(&ß)
return &buf
}
func DBufferChar(b *[]byte) rune {
return rune((*b)[0])
}
func DBufferChar2(b []byte) rune {
return rune(b[0])
}
func DBufferChar3(b byte) rune {
return rune(b)
}
func CharDBuffer(c *rune) []byte {
return []byte{byte(*c)}
}
func CharDBuffer2(z rune) []byte {
return CharDBuffer(&z)
}
func CharDBuffer3(z rune) *[]byte {
buf := CharDBuffer(&z)
return &buf
}
func DBufferInt(b *[]byte) int16 {
return int16(binary.BigEndian.Uint16(*b))
}
func DBufferInt2(b []byte) int16 {
return int16(binary.BigEndian.Uint16(b))
}
func IntDBuffer(i *int16) []byte {
b := make([]byte, 2, 2)
binary.BigEndian.PutUint16(b, uint16(*i)) // Das Memory Layout ist bei uint32 und int32 identisch
return b
}
func IntDBuffer2(j int16) []byte {
return IntDBuffer(&j)
}
func IntDBuffer3(j int16) *[]byte {
buf := IntDBuffer(&j)
return &buf
}
func DBufferDInt(b *[]byte) int32 {
return int32(binary.BigEndian.Uint32(*b)) // Das Memory Layout ist bei uint32 und int32 identisch
}
func DBufferDInt2(b []byte) int32 {
return int32(binary.BigEndian.Uint32(b)) // Das Memory Layout ist bei uint32 und int32 identisch
}
func DIntDBuffer(i *int32) []byte {
b := make([]byte, 4, 4)
binary.BigEndian.PutUint32(b, uint32(*i)) // Das Memory Layout ist bei uint32 und int32 identisch
return b
}
func DIntDBuffer2(j int32) []byte {
return DIntDBuffer(&j)
}
func DIntDBuffer3(j int32) *[]byte {
buf := DIntDBuffer(&j)
return &buf
}
func DBufferReal(b *[]byte) float32 {
return float32(math.Float32frombits(binary.BigEndian.Uint32(*b)))
}
func DBufferReal2(b []byte) float32 {
return float32(math.Float32frombits(binary.BigEndian.Uint32(b)))
}
func RealDBuffer(r *float32) []byte {
b := make([]byte, 4, 4)
binary.BigEndian.PutUint32(b, math.Float32bits(*r))
return b
}
func RealDBuffer2(f float32) []byte {
return RealDBuffer(&f)
}
func RealDBuffer3(f float32) *[]byte {
buf := RealDBuffer(&f)
return &buf
}
func DBufferByte(b *[]byte) uint8 {
return uint8((*b)[0])
}
func DBufferByte2(b []byte) uint8 {
return uint8(b[0])
}
func DBufferByte3(b byte) uint8 {
return uint8(b)
}
func ByteDBuffer(i *uint8) []byte {
return []byte{*i}
}
func ByteDBuffer2(j uint8) []byte {
return ByteDBuffer(&j)
}
func ByteDBuffer3(j uint8) *[]byte {
buf := ByteDBuffer(&j)
return &buf
}
func DBufferWord(b *[]byte) uint16 {
return uint16(binary.BigEndian.Uint16(*b))
}
func DBufferWord2(b []byte) uint16 {
return uint16(binary.BigEndian.Uint16(b))
}
func WordDBuffer(i *uint16) []byte {
b := make([]byte, 2, 2)
binary.BigEndian.PutUint16(b, uint16(*i)) // Das Memory Layout ist bei uint32 und int32 identisch
return b
}
func WordDBuffer2(j uint16) []byte {
return WordDBuffer(&j)
}
func WordDBuffer3(j uint16) *[]byte {
buf := WordDBuffer(&j)
return &buf
}
func DBufferDWord(b *[]byte) uint32 {
return uint32(binary.BigEndian.Uint32(*b)) // Das Memory Layout ist bei uint32 und int32 identisch
}
func DBufferDWord2(b []byte) uint32 {
return uint32(binary.BigEndian.Uint32(b)) // Das Memory Layout ist bei uint32 und int32 identisch
}
func DWordDBuffer(i *uint32) []byte {
b := make([]byte, 4, 4)
binary.BigEndian.PutUint32(b, uint32(*i)) // Das Memory Layout ist bei uint32 und int32 identisch
return b
}
func DWordDBuffer2(j uint32) []byte {
return DWordDBuffer(&j)
}
func DWordDBuffer3(j uint32) *[]byte {
buf := DWordDBuffer(&j)
return &buf
}
/*
//Siehe https://en.wikipedia.org/wiki/Binary-coded_decimal und https://stackoverflow.com/questions/4494664/binary-coded-decimal-bcd-to-hexadecimal-conversion
func Bcd2int(bcd byte) int {
//return int((bcd & 0x0F) + ((bcd >> 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))
}
}

16
snap7a.go Normal file
View File

@ -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
}

33
snap7b.h Normal file
View File

@ -0,0 +1,33 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#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 <windows.h>
#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);
}

32
tools.go Normal file
View File

@ -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
}

705
wrap7.go Normal file
View File

@ -0,0 +1,705 @@
// wrap7 project wrap7.go
package wrap7
/*
#cgo CFLAGS: -O2 -march=native
#cgo LDFLAGS: -lm -lsnap7
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <snap7b.h>
#ifdef OS_WINDOWS
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#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
}