2024-02-08 18:23:46 +00:00
package main
import (
2024-02-09 22:32:32 +00:00
"flag"
2024-02-08 18:23:46 +00:00
"fmt"
2024-02-19 18:52:40 +00:00
"html/template"
2024-02-11 21:48:05 +00:00
"log"
2024-02-08 18:23:46 +00:00
"net"
2024-02-09 22:32:32 +00:00
"os"
2024-03-03 12:54:42 +00:00
"path/filepath"
2024-02-08 19:57:16 +00:00
"strconv"
2024-02-09 22:32:32 +00:00
"strings"
"sync"
2024-03-23 09:26:36 +00:00
"sync/atomic"
2024-02-09 22:32:32 +00:00
"time"
2024-02-08 18:23:46 +00:00
2024-03-04 20:11:34 +00:00
"git.sr.ht/~michalr/go-satel"
2024-02-08 18:23:46 +00:00
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
2024-02-09 22:32:32 +00:00
const (
2024-03-06 18:39:43 +00:00
PersistenceFilename = "hs_wro_last_seen.bin"
2024-03-23 09:26:36 +00:00
retryDelaySec = 25
2024-02-09 22:32:32 +00:00
)
2024-02-11 10:51:41 +00:00
type RealSleeper struct {
duration time . Duration
}
func ( self RealSleeper ) Sleep ( ch chan <- interface { } ) {
2024-02-11 21:51:33 +00:00
go func ( ) {
time . Sleep ( self . duration )
ch <- nil
} ( )
2024-02-11 10:51:41 +00:00
}
2024-03-23 09:40:20 +00:00
func getCmdLineParams ( logger * log . Logger ) AppConfig {
2024-02-11 13:56:03 +00:00
satelApiAddr := flag . String ( "satel-addr" , "" , "Address that should be used to connect to the SATEL device" )
satelApiPort := flag . String ( "satel-port" , "7094" , "Port that should be used to connect to the SATEL device" )
chatIdRaw := flag . String ( "tg-chat-id" , "" , "Telegram Chat ID where to send updates. Use \",\" to specify multiple IDs." )
2024-02-18 17:44:08 +00:00
allowedTypesRaw := flag . String ( "allowed-types" , "" , "Satel change types that are allowed. All other types will be discarded. By default all are allowed. Use \",\" to specify multiple types." )
2024-02-18 18:00:49 +00:00
allowedIndexesRaw := flag . String ( "allowed-indexes" , "" , "Satel indexes (zones?) that are allowed. All other indexes will be discarded. By default all are allowed. Use \",\" to specify multiple indexes." )
2024-03-06 07:00:39 +00:00
satelPoolInterval := flag . Duration ( "pool-interval" , 5 * time . Second , "How often should the SATEL device be pooled for changes? Default: 5 seconds." )
2024-02-08 18:23:46 +00:00
flag . Parse ( )
2024-02-11 13:56:03 +00:00
if len ( * satelApiAddr ) == 0 || len ( * satelApiPort ) == 0 || len ( * chatIdRaw ) == 0 {
2024-02-11 21:48:05 +00:00
logger . Fatal ( "Use --satel-addr=ADDR, --satel-port=PORT and --tg-chat-id=CHAT_ID command line flags to continue." )
2024-02-08 18:23:46 +00:00
}
2024-02-11 13:56:03 +00:00
chatIdsStrings := strings . Split ( * chatIdRaw , "," )
var chatIds [ ] int64
for _ , chatIdStr := range chatIdsStrings {
chatId , err := strconv . ParseInt ( chatIdStr , 10 , 64 )
2024-02-08 19:57:16 +00:00
if err != nil {
2024-02-11 21:48:05 +00:00
logger . Fatalf ( "Tried to use a non-int value for one of tg_chatIds: %s. That's bad." , chatIdStr )
2024-02-08 19:57:16 +00:00
}
2024-02-11 13:56:03 +00:00
chatIds = append ( chatIds , chatId )
2024-02-08 19:57:16 +00:00
}
2024-02-18 17:44:08 +00:00
allowedTypesStrings := strings . Split ( * allowedTypesRaw , "," )
var allowedTypes [ ] satel . ChangeType
for _ , allowedTypeStr := range allowedTypesStrings {
2024-02-18 18:19:10 +00:00
if len ( allowedTypeStr ) == 0 {
continue
}
2024-02-18 17:44:08 +00:00
allowedType , err := StringToSatelChangeType ( allowedTypeStr )
if err != nil {
logger . Fatalf ( "Error trying to understand an allowed type: %s." , err )
}
allowedTypes = append ( allowedTypes , allowedType )
}
2024-02-18 18:00:49 +00:00
allowedIndexesStrings := strings . Split ( * allowedIndexesRaw , "," )
var allowedIndexes [ ] int
for _ , allowedIndexStr := range allowedIndexesStrings {
2024-02-18 18:19:10 +00:00
if len ( allowedIndexStr ) == 0 {
continue
}
2024-02-18 18:00:49 +00:00
allowedIndex , err := strconv . ParseInt ( allowedIndexStr , 10 , 0 )
if err != nil {
logger . Fatalf ( "Tried to use a non-int value for one of allowed indexes: %s. That's bad." , allowedIndexStr )
}
allowedIndexes = append ( allowedIndexes , int ( allowedIndex ) )
}
2024-02-08 18:23:46 +00:00
2024-02-11 13:56:03 +00:00
satelAddr := fmt . Sprintf ( "%s:%s" , * satelApiAddr , * satelApiPort )
2024-03-23 09:40:20 +00:00
return AppConfig { satelAddr , chatIds , allowedTypes , allowedIndexes , * satelPoolInterval }
2024-02-11 13:56:03 +00:00
}
2024-03-06 07:00:39 +00:00
func makeSatel ( satelAddr string , poolInterval time . Duration ) * satel . Satel {
2024-02-11 13:56:03 +00:00
satelConn , err := net . Dial ( "tcp" , satelAddr )
2024-02-08 18:23:46 +00:00
if err != nil {
panic ( err )
}
2024-03-06 07:00:39 +00:00
return satel . NewConfig ( satelConn , satel . Config { EventsQueueSize : 10 , PoolingInterval : poolInterval } )
2024-02-11 13:56:03 +00:00
}
2024-03-03 12:54:42 +00:00
func getPersistenceFilePath ( ) string {
var stateDir = os . Getenv ( "STATE_DIRECTORY" )
if len ( stateDir ) != 0 {
return filepath . Join ( stateDir , PersistenceFilename )
}
return PersistenceFilename
}
2024-02-11 13:56:03 +00:00
func main ( ) {
var (
2024-03-23 09:26:36 +00:00
wg sync . WaitGroup
tgEvents = make ( chan GenericMessage , 5 )
logger = log . New ( os . Stderr , "Main" , log . Lmicroseconds )
sleeper = RealSleeper { time . Second * 60 }
stopRequested = atomic . Bool { }
2024-02-11 13:56:03 +00:00
)
2024-03-23 09:26:36 +00:00
stopRequested . Store ( false )
2024-03-23 09:40:20 +00:00
cfg := getCmdLineParams ( logger )
2024-02-11 13:56:03 +00:00
2024-03-23 09:40:20 +00:00
s := makeSatel ( cfg . satelAddr , cfg . poolInterval )
logger . Printf ( "Connected to Satel: %s" , cfg . satelAddr )
2024-02-11 13:56:03 +00:00
2024-02-08 18:23:46 +00:00
bot , err := tgbotapi . NewBotAPI ( os . Getenv ( "TELEGRAM_APITOKEN" ) )
2024-02-09 22:32:32 +00:00
if err != nil {
panic ( err )
}
2024-02-11 21:48:05 +00:00
logger . Print ( "Created Telegram Bot API client" )
2024-02-09 22:32:32 +00:00
2024-03-23 09:40:20 +00:00
tgSender := TgSender { bot , s , log . New ( os . Stderr , "TgFormatter" , log . Lmicroseconds ) , cfg . chatIds }
2024-02-18 16:28:32 +00:00
2024-02-19 18:52:40 +00:00
tpl := template . Must ( template . New ( "TelegramMessage" ) . Parse ( TelegramMessageTemplate ) )
2024-03-03 12:54:42 +00:00
dataStore := MakeDataStore ( log . New ( os . Stderr , "DataStore" , log . Lmicroseconds ) , getPersistenceFilePath ( ) )
2024-02-28 20:26:20 +00:00
2024-03-10 09:52:27 +00:00
Consume (
SendToTg ( Throttle ( NotifyViaHTTP ( tgEvents , & wg , log . New ( os . Stderr , "HTTPNotify" , log . Lmicroseconds ) ) ,
& wg , sleeper , log . New ( os . Stderr , "MessageThrottle" , log . Lmicroseconds ) ) ,
tgSender , & wg , log . New ( os . Stderr , "SendToTg" , log . Lmicroseconds ) , tpl ) ,
2024-03-06 21:31:08 +00:00
)
2024-02-18 16:28:32 +00:00
2024-03-23 09:26:36 +00:00
go CloseSatelOnCtrlC ( s , & stopRequested )
2024-02-18 18:36:54 +00:00
2024-03-23 09:26:36 +00:00
for stopRequested . Load ( ) == false {
for e := range FilterByTypeOrIndex (
FilterByLastSeen ( s . Events , & wg , & dataStore , log . New ( os . Stderr , "FilterByLastSeen" , log . Lmicroseconds ) ) ,
2024-03-23 09:40:20 +00:00
& wg , cfg . allowedTypes , cfg . allowedIndexes ) {
2024-03-23 09:26:36 +00:00
logger . Print ( "Received change from SATEL: " , e )
tgEvents <- GenericMessage { e . BasicEvents }
}
if stopRequested . Load ( ) == false {
logger . Printf ( "Waiting %d seconds before trying again" , retryDelaySec )
time . Sleep ( retryDelaySec * time . Second )
}
2024-02-08 18:23:46 +00:00
}
2024-02-09 22:32:32 +00:00
2024-02-11 13:56:03 +00:00
close ( tgEvents )
2024-02-09 22:32:32 +00:00
wg . Wait ( )
2024-02-08 18:23:46 +00:00
}