package satel import ( "bufio" "errors" "net" "sync" "time" ) type BasicEventElement struct { Type ChangeType Index int Value bool } type Event struct { BasicEvents []BasicEventElement } type Config struct { EventsQueueSize int LongCommands bool PoolingInterval time.Duration } 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 callCommandAndRetrieveResult(cmd byte, s *Satel) func() { return func() { err := s.sendCmd(cmd) if err != nil { return } s.readRawEvents() } } 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 <- callCommandAndRetrieveResult(SatelCommand_armedPartitionsReally, s) s.commandQueue <- callCommandAndRetrieveResult(SatelCommand_partitionsAlarm, s) time.Sleep(config.PoolingInterval) } }() 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 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 == Satel_Result { return } basicElements := make([]BasicEventElement, 0) for i, bb := range bytes { for j := 0; j < 8; j++ { index := byte(1 << j) basicElements = append(basicElements, BasicEventElement{ Type: ChangeType(cmd), Index: i*8 + j, Value: bb&index != 0, }) } } s.Events <- Event{basicElements} } 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 }