wrap7/helper.go

564 lines
16 KiB
Go

// 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))
}
}