mirror of https://git.sr.ht/~michalr/go-satel
233 lines
4.6 KiB
Go
233 lines
4.6 KiB
Go
package satel
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"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
|
|
rawEvents chan []byte
|
|
commandQueue chan func()
|
|
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),
|
|
rawEvents: make(chan []byte, config.EventsQueueSize),
|
|
commandQueue: make(chan func(), config.EventsQueueSize),
|
|
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 {
|
|
s.commandQueue <- func() {
|
|
err = s.sendCmd(0x0A)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.readRawEvents()
|
|
}
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
}()
|
|
go func() {
|
|
for f := range s.commandQueue {
|
|
f()
|
|
}
|
|
}()
|
|
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 (s *Satel) GetName(devType DeviceType, devIndex byte) NameEvent {
|
|
resultChan := make(chan NameEvent)
|
|
|
|
s.commandQueue <- func() {
|
|
err := s.sendCmd(0xEE, byte(devType), devIndex)
|
|
if err != nil {
|
|
resultChan <- NameEvent{
|
|
DevType: DeviceType(devType),
|
|
DevNumber: devIndex,
|
|
DevTypeFunction: 0,
|
|
DevName: fmt.Sprint("ERROR RETRIEVING NAME ", devType, devIndex),
|
|
}
|
|
}
|
|
|
|
var bytes = <-s.rawEvents
|
|
cmd := bytes[0]
|
|
bytes = bytes[1 : len(bytes)-2]
|
|
if cmd == 0xEF {
|
|
resultChan <- NameEvent{
|
|
DevType: DeviceType(devType),
|
|
DevNumber: devIndex,
|
|
DevTypeFunction: 0,
|
|
DevName: fmt.Sprint("NO NAME ", devType, devIndex),
|
|
}
|
|
} else if cmd == 0xEE {
|
|
resultChan <- makeNameEvent(bytes)
|
|
} else {
|
|
resultChan <- NameEvent{
|
|
DevType: DeviceType(devType),
|
|
DevNumber: devIndex,
|
|
DevTypeFunction: 0,
|
|
DevName: fmt.Sprint("ERROR GETTING NAME ", devType, devIndex),
|
|
}
|
|
}
|
|
}
|
|
return <-resultChan
|
|
}
|
|
|
|
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) readRawEvents() {
|
|
var bytes = <-s.rawEvents
|
|
cmd := bytes[0]
|
|
bytes = bytes[1 : len(bytes)-2]
|
|
if cmd == 0xEF {
|
|
return
|
|
}
|
|
for i, bb := range bytes {
|
|
for j := 0; j < 8; j++ {
|
|
index := byte(1 << j)
|
|
s.Events <- Event{
|
|
Type: ChangeType(cmd),
|
|
Index: i*8 + j,
|
|
Value: bb&index != 0,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Satel) read() {
|
|
scanner := bufio.NewScanner(s.conn)
|
|
scanner.Split(scan)
|
|
|
|
for ok := scanner.Scan(); ok; ok = scanner.Scan() {
|
|
bytes := scanner.Bytes()
|
|
s.cmdRes()
|
|
s.rawEvents <- bytes
|
|
}
|
|
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
|
|
}
|