1
0
Fork 0

Initial commit

This commit is contained in:
Przemko 2021-11-19 14:15:31 +01:00
commit 030942c3e9
9 changed files with 366 additions and 0 deletions

25
.github/workflows/build.yml vendored Normal file
View File

@ -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 Normal file
View File

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

78
change_type.go Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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

11
go.sum Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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