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