github.com/futurehomeno/fimpgo@v1.14.0/sync_client.go (about) 1 package fimpgo 2 3 import ( 4 "time" 5 6 "github.com/google/uuid" 7 log "github.com/sirupsen/logrus" 8 ) 9 10 // SyncClient allows sync interaction over async channel. 11 type SyncClient struct { 12 mqttTransport *MqttTransport 13 mqttConnPool *MqttConnectionPool 14 isConnPoolEnabled bool 15 transactionPoolSize int // Max transaction pool size 16 inboundBufferSize int // Inbound message channel buffer size 17 inboundMsgChannel MessageCh 18 inboundChannelName string 19 stopSignalCh chan bool 20 isStartedUsingConnect bool 21 globalPrefix string 22 } 23 24 // SetGlobalPrefix configures global prefix/site_id . Most be used from backend services. 25 func (sc *SyncClient) SetGlobalPrefix(globalPrefix string) { 26 sc.globalPrefix = globalPrefix 27 } 28 29 func (sc *SyncClient) SetTransactionPoolSize(transactionPoolSize int) { 30 sc.transactionPoolSize = transactionPoolSize 31 } 32 33 // NewSyncClient creates sync client using existing mqtt connection 34 func NewSyncClient(mqttTransport *MqttTransport) *SyncClient { 35 sc := SyncClient{mqttTransport: mqttTransport} 36 sc.transactionPoolSize = 20 37 sc.inboundBufferSize = 10 38 sc.init() 39 return &sc 40 } 41 42 // NewSyncClientV2 creates new sync client using existing mqtt connection and configures transactionPool size and inboundBufferSize 43 func NewSyncClientV2(mqttTransport *MqttTransport, transactionPoolSize int, inboundBuffSize int) *SyncClient { 44 sc := SyncClient{mqttTransport: mqttTransport} 45 sc.transactionPoolSize = transactionPoolSize 46 sc.inboundBufferSize = inboundBuffSize 47 sc.init() 48 return &sc 49 } 50 51 func (sc *SyncClient) SetConfigs(transactionPoolSize int, inboundBuffSize int) { 52 if transactionPoolSize == 0 { 53 transactionPoolSize = 20 54 } 55 if inboundBuffSize == 0 { 56 inboundBuffSize = 10 57 } 58 } 59 60 func (sc *SyncClient) init() { 61 sc.stopSignalCh = make(chan bool) 62 } 63 64 // Connect establishes internal connection to mqtt broker and initializes mqtt 65 // Should be used if MqttTransport instance is not provided in constructor . 66 func (sc *SyncClient) Connect(serverURI string, clientID string, username string, password string, cleanSession bool, subQos byte, pubQos byte) error { 67 if sc.mqttTransport == nil { 68 log.Info("<SyncClient> Connecting to mqtt broker") 69 sc.mqttTransport = NewMqttTransport(serverURI, clientID, username, password, cleanSession, subQos, pubQos) 70 err := sc.mqttTransport.Start() 71 if err != nil { 72 log.Error("<SyncClient> Error connecting to broker :", err) 73 return err 74 } 75 sc.isStartedUsingConnect = true 76 } else { 77 log.Info("<SyncClient> Already connected") 78 } 79 80 return nil 81 } 82 83 // Stop has to be invoked to stop message listener 84 func (sc *SyncClient) Stop() { 85 if sc.isStartedUsingConnect { 86 sc.mqttTransport.Stop() 87 } 88 89 } 90 91 // AddSubscription has to be invoked before Send methods 92 func (sc *SyncClient) AddSubscription(topic string) { 93 if err := sc.mqttTransport.Subscribe(topic); err != nil { 94 log.Error("<SyncClient> error subscribing to topic:", err) 95 } 96 } 97 98 // RemoveSubscription 99 func (sc *SyncClient) RemoveSubscription(topic string) { 100 if err := sc.mqttTransport.Unsubscribe(topic); err != nil { 101 log.Error("<SyncClient> error unsubscribing from topic:", err) 102 } 103 } 104 105 // SendFimpWithTopicResponse send message over mqtt and awaits response from responseTopic with responseService and responseMsgType 106 func (sc *SyncClient) sendFimpWithTopicResponse(topic string, fimpMsg *FimpMessage, responseTopic string, responseService string, responseMsgType string, timeout int64, autoSubscribe bool) (*FimpMessage, error) { 107 //log.Debug("Registering request uid = ",fimpMsg.UID) 108 var conId int 109 var conn *MqttTransport 110 var inboundCh = make(MessageCh, 10) 111 var responseChannel chan *FimpMessage 112 var err error 113 var chanName = uuid.New().String() 114 115 defer func() { 116 if autoSubscribe && responseTopic != "" && conn != nil { 117 if err := conn.Unsubscribe(responseTopic); err != nil { 118 log.Error("<SyncClient> error unsubscribing from topic:", err) 119 } 120 } 121 if conn != nil { 122 conn.UnregisterChannel(chanName) 123 close(inboundCh) 124 if sc.isConnPoolEnabled { 125 // force unset global prefix 126 conn.SetGlobalTopicPrefix("") 127 sc.mqttConnPool.ReturnConnection(conId) 128 } 129 } 130 }() 131 132 if sc.isConnPoolEnabled { 133 conId, conn, err = sc.mqttConnPool.BorrowConnection() 134 if err != nil { 135 return nil, err 136 } 137 } else { 138 conn = sc.mqttTransport 139 } 140 conn.RegisterChannel(chanName, inboundCh) 141 142 responseChannel = sc.startResponseListener(fimpMsg, responseMsgType, responseService, responseTopic, inboundCh, timeout) 143 144 // force the global prefix -> this is useful for per-site operations 145 if sc.globalPrefix != "" { 146 conn.SetGlobalTopicPrefix(sc.globalPrefix) 147 } 148 149 if autoSubscribe && responseTopic != "" { 150 if err := conn.Subscribe(responseTopic); err != nil { 151 log.Error("<SyncClient> error subscribing to topic:", err) 152 } 153 } else if responseTopic != "" { 154 if err := conn.Subscribe(responseTopic); err != nil { 155 log.Error("<SyncClient> error subscribing to topic:", err) 156 return nil, errSubscribe 157 } 158 } 159 160 if err := conn.PublishToTopic(topic, fimpMsg); err != nil { 161 log.Error("<SyncClient> error publishing to topic:", err) 162 return nil, errPublish 163 } 164 165 select { 166 case fimpResponse := <-responseChannel: 167 return fimpResponse, nil 168 case <-time.After(time.Second * time.Duration(timeout)): 169 log.Info("<SyncClient> No response from queue for ", timeout) 170 return nil, errTimeout 171 } 172 } 173 174 // SendReqRespFimp sends msg to topic and expects to receive response on response topic . If autoSubscribe is set to true , the system will automatically subscribe and unsubscribe from response topic. 175 func (sc *SyncClient) SendReqRespFimp(cmdTopic, responseTopic string, reqMsg *FimpMessage, timeout int64, autoSubscribe bool) (*FimpMessage, error) { 176 return sc.sendFimpWithTopicResponse(cmdTopic, reqMsg, responseTopic, "", "", timeout, autoSubscribe) 177 } 178 179 // SendFimp sends message over mqtt and blocks until request is received or timeout is reached . 180 // messages are correlated using uid->corid 181 func (sc *SyncClient) SendFimp(topic string, fimpMsg *FimpMessage, timeout int64) (*FimpMessage, error) { 182 return sc.SendFimpWithTopicResponse(topic, fimpMsg, "", "", "", timeout) 183 } 184 185 // SendFimpWithTopicResponse send message over mqtt and awaits response from responseTopic with responseService and responseMsgType (the method is for backward compatibility) 186 func (sc *SyncClient) SendFimpWithTopicResponse(topic string, fimpMsg *FimpMessage, responseTopic string, responseService string, responseMsgType string, timeout int64) (*FimpMessage, error) { 187 return sc.sendFimpWithTopicResponse(topic, fimpMsg, responseTopic, responseService, responseMsgType, timeout, false) 188 } 189 190 // startResponseListener starts response listener , it blocks callers proc until response is received or timeout. 191 func (sc *SyncClient) startResponseListener(requestMsg *FimpMessage, respMsgType, respService, respTopic string, inboundCh MessageCh, timeout int64) chan *FimpMessage { 192 log.Debug("<SyncClient> Msg listener is started") 193 respChan := make(chan *FimpMessage) 194 go func() { 195 for msg := range inboundCh { 196 if (respMsgType == msg.Payload.Type && respService == msg.Payload.Service && respTopic == msg.Topic) || requestMsg.UID == msg.Payload.CorrelationID { 197 select { 198 case respChan <- msg.Payload: 199 case <-time.After(time.Second * time.Duration(timeout)): 200 } 201 return 202 } 203 } 204 }() 205 return respChan 206 }