main
Przemko 2021-11-19 14:15:31 +01:00
commit 030942c3e9
9 zmienionych plików z 366 dodań i 0 usunięć

25
.github/workflows/build.yml vendored 100644
Wyświetl plik

@ -0,0 +1,25 @@
name: Build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Build
run: go build -v .
- name: Test
run: go test -v .

2
.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,2 @@
/go-satel.iml
/.idea/

78
change_type.go 100644
Wyświetl plik

@ -0,0 +1,78 @@
package satel
type ChangeType byte
const (
ZoneViolation ChangeType = iota
ZoneTamper
ZoneAlarm
ZoneTamperAlarm
ZoneAlarmMemory
ZoneTamperAlarmMemory
ZoneBypass
ZoneNoViolationTrouble
ZoneLongViolationTrouble
ArmedPartitionSuppressed
ArmedPartition
PartitionArmedInMode2
PartitionArmedInMode3
PartitionWith1stCodeEntered
PartitionEntryTime
PartitionExitTimeOver10s
PartitionExitTimeUnder10s
PartitionTemporaryBlocked
PartitionBlockedForGuardRound
PartitionAlarm
PartitionFireAlarm
PartitionAlarmMemory
PartitionFireAlarmMemory
Output
DoorOpened
DoorOpenedLong
StatusBit
TroublePart1
TroublePart2
TroublePart3
TroublePart4
TroublePart5
TroubleMemoryPart1
TroubleMemoryPart2
TroubleMemoryPart3
TroubleMemoryPart4
TroubleMemoryPart5
PartitionWithViolatedZones
ZoneIsolate
)
func (c ChangeType) String() string {
strings := [...]string{
"zone-violation",
"zone-tamper",
"zone-alarm",
"zone-tamper-alarm",
"zone-alarm-memory",
"zone-tamper-alarm-memory",
"zone-bypass",
"zone-no-violation-trouble",
"zone-long-violation-trouble",
"armed-partition-suppressed",
"armed-partition",
"partition-armed-mode-2",
"partition-armed-mode-3",
"partition-with-1st-code-entered",
"partition-entry-time",
"partition-exit-time-over-10s",
"partition-exit-time-under-10s",
"partition-temporary-blocked",
"partition-blocked-guard-round",
"partition-alarm",
"partition-fire-alarm",
"partition-alarm-memory",
"partition-fire-alarm-memory",
"output"}
if int(c) < len(strings) {
return strings[c]
} else {
return "unknown"
}
}

24
frame.go 100644
Wyświetl plik

@ -0,0 +1,24 @@
package satel
import "math/bits"
const seed uint16 = 0x147A
func frame(data ...byte) []byte {
f := append([]byte{0xFE, 0xFE}, data...)
f = append(f, crc(data)...)
return append(f, 0xFE, 0x0D)
}
func crc(data []byte) []byte {
c := seed
for _, b := range data {
c = update(c, b)
}
return []byte{byte(c >> 8), byte(c & 0xFF)}
}
func update(c uint16, b byte) uint16 {
c = bits.RotateLeft16(c, 1)
c ^= 0xFFFF
return c + c>>8 + uint16(b)
}

5
go.mod 100644
Wyświetl plik

@ -0,0 +1,5 @@
module go-satel
go 1.16
require github.com/stretchr/testify v1.7.0

11
go.sum 100644
Wyświetl plik

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

184
satel.go 100644
Wyświetl plik

@ -0,0 +1,184 @@
package satel
import (
"bufio"
"errors"
"net"
"sync"
"time"
)
type Event struct {
Type ChangeType
Index int
Value bool
}
type Config struct {
EventsQueueSize int
LongCommands bool
}
type Satel struct {
conn net.Conn
mu sync.Mutex
cmdSize int
cmdChan chan int
Events chan Event
}
func New(conn net.Conn) *Satel {
return NewConfig(conn, Config{})
}
func NewConfig(conn net.Conn, config Config) *Satel {
s := &Satel{
conn: conn,
cmdChan: make(chan int),
Events: make(chan Event, config.EventsQueueSize),
}
if config.LongCommands {
s.cmdSize = 32
} else {
s.cmdSize = 16
}
go s.read()
err := s.sendCmd(0x7F, 0x01, 0x04, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
if err != nil {
close(s.Events)
return s
}
go func() {
for {
err = s.sendCmd(0x0A)
if err != nil {
return
}
time.Sleep(5 * time.Second)
}
}()
return s
}
func (s *Satel) ArmPartition(code string, mode, index int) error {
data := make([]byte, 4)
data[index/8] = 1 << (index % 8)
bytes := prepareCommand(code, byte(0x80+mode), data...)
return s.sendCmd(bytes...)
}
func (s *Satel) ForceArmPartition(code string, mode, index int) error {
data := make([]byte, 4)
data[index/8] = 1 << (index % 8)
bytes := prepareCommand(code, byte(0xA0+mode), data...)
return s.sendCmd(bytes...)
}
func (s *Satel) DisarmPartition(code string, index int) error {
data := make([]byte, 4)
data[index/8] = 1 << (index % 8)
bytes := prepareCommand(code, byte(0x84), data...)
return s.sendCmd(bytes...)
}
func (s *Satel) SetOutput(code string, index int, value bool) error {
cmd := byte(0x89)
if value {
cmd = 0x88
}
data := make([]byte, s.cmdSize)
data[index/8] = 1 << (index % 8)
bytes := prepareCommand(code, cmd, data...)
return s.sendCmd(bytes...)
}
func prepareCommand(code string, cmd byte, data ...byte) []byte {
bytes := append([]byte{cmd}, transformCode(code)...)
return append(bytes, data...)
}
func (s *Satel) Close() error {
return s.conn.Close()
}
type command struct {
prev [32]byte
initialized bool
}
func (s *Satel) read() {
scanner := bufio.NewScanner(s.conn)
scanner.Split(scan)
commands := make(map[byte]command)
for ok := scanner.Scan(); ok; ok = scanner.Scan() {
bytes := scanner.Bytes()
cmd := bytes[0]
bytes = bytes[1 : len(bytes)-2]
s.cmdRes()
if cmd == 0xEF {
continue
}
c := commands[cmd]
for i, bb := range bytes {
change := bb ^ c.prev[i]
for j := 0; j < 8; j++ {
index := byte(1 << j)
if !c.initialized || change&index != 0 {
s.Events <- Event{
Type: ChangeType(cmd),
Index: i*8 + j,
Value: bb&index != 0,
}
}
}
c.prev[i] = bytes[i]
}
c.initialized = true
commands[cmd] = c
}
close(s.Events)
_ = s.conn.Close()
}
func (s *Satel) cmdRes() {
select {
case s.cmdChan <- 0:
default:
}
}
func (s *Satel) sendCmd(data ...byte) (err error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.conn == nil {
return errors.New("no connection")
}
_, err = s.conn.Write(frame(data...))
if err == nil {
select {
case <-s.cmdChan:
case <-time.After(3 * time.Second):
}
}
return
}
func transformCode(code string) []byte {
bytes := make([]byte, 8)
for i := 0; i < 16; i++ {
if i < len(code) {
digit := code[i]
if i%2 == 0 {
bytes[i/2] = (digit - '0') << 4
} else {
bytes[i/2] |= digit - '0'
}
} else if i%2 == 0 {
bytes[i/2] = 0xFF
} else if i == len(code) {
bytes[i/2] |= 0x0F
}
}
return bytes
}

16
satel_test.go 100644
Wyświetl plik

@ -0,0 +1,16 @@
package satel
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestTransformCode(t *testing.T) {
assert := assert.New(t)
assert.Equal([]byte{0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, transformCode("0"))
assert.Equal([]byte{0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, transformCode("0000"))
assert.Equal([]byte{0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, transformCode("000"))
assert.Equal([]byte{0x12, 0x34, 0x56, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, transformCode("123456"))
assert.Equal([]byte{0x98, 0x12, 0x4F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, transformCode("98124"))
assert.Equal([]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0xFF}, transformCode("12345678901234"))
}

21
scanner.go 100644
Wyświetl plik

@ -0,0 +1,21 @@
package satel
import "bytes"
func scan(data []byte, _ bool) (advance int, token []byte, err error) {
i := 0
for ; i < len(data) && data[i] == 0xFE; i++ {
}
if i > 0 {
data = data[i:]
}
startIndex := bytes.Index(data, []byte{0xFE, 0xFE})
index := bytes.Index(data, []byte{0xFE, 0x0D})
if startIndex > 0 && (index < 0 || startIndex < index) {
return i + startIndex + 2, nil, nil
}
if index > 0 {
return i + index + 2, data[:index], nil
}
return 0, nil, nil
}