1212
1313MAX_DECIMAL_POINTS = 8
1414
15+
1516class mqtt_interface ():
16- def __init__ (self , hostname , port , username , password , config_file , mqtt_topic_prefix ):
17+ def __init__ (self , hostname , port , username , password , config_file , mqtt_topic_prefix ,
18+ use_tls = True , insecure = False , cafile = None , cert = None , key = None ):
1719 self .hostname = hostname
1820 self ._port = port
1921 self .username = username
2022 self .password = password
2123 self .config = self ._load_modbus_config (config_file )
24+ self .use_tls = use_tls
25+ self .insecure = insecure
26+ self .cafile = cafile
27+ self .cert = cert
28+ self .key = key
2229 if not mqtt_topic_prefix .endswith ('/' ):
2330 mqtt_topic_prefix = mqtt_topic_prefix + '/'
2431 self .prefix = mqtt_topic_prefix
2532 self .address_offset = self .config .get ('address_offset' , 0 )
2633 self .registers = self .config ['registers' ]
2734 for register in self .registers :
2835 register ['address' ] += self .address_offset
29- self .modbus_connect_retries = - 1 # Retry forever by default
30- self .modbus_reconnect_sleep_interval = 5 # Wait this many seconds between modbus connection attempts
36+ self .modbus_connect_retries = - 1 # Retry forever by default
37+ self .modbus_reconnect_sleep_interval = 5 # Wait this many seconds between modbus connection attempts
3138
3239 def connect (self ):
3340 # Connects to modbus and MQTT.
@@ -65,6 +72,9 @@ def connect_mqtt(self):
6572 self ._mqtt_client ._on_disconnect = self ._on_disconnect
6673 self ._mqtt_client ._on_message = self ._on_message
6774 self ._mqtt_client ._on_subscribe = self ._on_subscribe
75+ if self .use_tls :
76+ self ._mqtt_client .tls_set (ca_certs = self .cafile , certfile = self .cert , keyfile = self .key )
77+ self ._mqtt_client .tls_insecure_set (self .insecure )
6878 self ._mqtt_client .connect (self .hostname , self ._port , 60 )
6979 self ._mqtt_client .loop_start ()
7080
@@ -87,8 +97,9 @@ def poll(self):
8797 for register in self ._get_registers_with ('pub_topic' ):
8898 try :
8999 value = self ._mb .get_value (register .get ('table' , 'holding' ), register ['address' ])
90- except :
91- logging .warning ("Couldn't get value from register {} in table {}" .format (register ['address' ], register .get ('table' , 'holding' )))
100+ except Exception :
101+ logging .warning ("Couldn't get value from register {} in table {}" .format (register ['address' ],
102+ register .get ('table' , 'holding' )))
92103 continue
93104 # Filter the value through the mask, if present.
94105 value &= register .get ('mask' , 0xFFFF )
@@ -157,24 +168,28 @@ def _on_message(self, client, userdata, msg):
157168 if 'value_map' in register :
158169 try :
159170 value = str (value , 'utf-8' )
160- if not value in register ['value_map' ]:
161- logging .warning ("Value not in value_map. Topic: {}, value: {}, valid values: {}" .format (topic , value , register ['value_map' ].keys ()))
171+ if value not in register ['value_map' ]:
172+ logging .warning ("Value not in value_map. Topic: {}, value: {}, valid values: {}" .format (topic ,
173+ value , register ['value_map' ].keys ()))
162174 continue
163175 # Map the value from the human-readable form into the raw modbus number
164176 value = register ['value_map' ][value ]
165177 except UnicodeDecodeError :
166- logging .warning ("Failed to decode MQTT payload as UTF-8. Can't compare it to the value_map for register {}" .format (register ))
178+ logging .warning ("Failed to decode MQTT payload as UTF-8. "
179+ "Can't compare it to the value_map for register {}" .format (register ))
167180 continue
168181 try :
169182 # Scale the value, if required.
170183 value = float (value )
171184 value = round (value / register .get ('scale' , 1 ))
172185 except ValueError :
173- logging .error ("Failed to convert register value for writing. Bad/missing value_map? Topic: {}, Value: {}" .format (topic , value ))
186+ logging .error ("Failed to convert register value for writing. "
187+ "Bad/missing value_map? Topic: {}, Value: {}" .format (topic , value ))
174188 continue
175189 type = register .get ('type' , 'uint16' )
176190 value = modbus_interface ._convert_from_type_to_uint16 (value , type )
177- self ._mb .set_value (register .get ('table' , 'holding' ), register ['address' ], int (value ), register .get ('mask' , 0xFFFF ))
191+ self ._mb .set_value (register .get ('table' , 'holding' ), register ['address' ], int (value ),
192+ register .get ('mask' , 0xFFFF ))
178193
179194 # This throws ValueError exceptions if the imported registers are invalid
180195 @staticmethod
@@ -197,27 +212,34 @@ def _validate_registers(registers):
197212 duplicate_json_keys [register ['pub_topic' ]] = []
198213 retain_setting [register ['pub_topic' ]] = set ()
199214 if 'json_key' in register and 'set_topic' in register :
200- raise ValueError ("Bad YAML configuration. Register with set_topic '{}' has a json_key specified. This is invalid. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details." .format (register ['set_topic' ]))
215+ raise ValueError ("Bad YAML configuration. Register with set_topic '{}' has a json_key specified. "
216+ "This is invalid. See https://github.com/tjhowse/modbus4mqtt/issues/23 for details."
217+ .format (register ['set_topic' ]))
201218 all_pub_topics .add (register ['pub_topic' ])
202219
203220 # Check that all registers with duplicate pub topics have json_keys
204221 for register in registers :
205222 if register ['pub_topic' ] in duplicate_pub_topics :
206223 if 'json_key' not in register :
207- raise ValueError ("Bad YAML configuration. pub_topic '{}' duplicated across registers without json_key field. Registers that share a pub_topic must also have a unique json_key." .format (register ['pub_topic' ]))
224+ raise ValueError ("Bad YAML configuration. pub_topic '{}' duplicated across registers without "
225+ "json_key field. Registers that share a pub_topic must also have a unique "
226+ "json_key." .format (register ['pub_topic' ]))
208227 if register ['json_key' ] in duplicate_json_keys [register ['pub_topic' ]]:
209- raise ValueError ("Bad YAML configuration. pub_topic '{}' duplicated across registers with a duplicated json_key field. Registers that share a pub_topic must also have a unique json_key." .format (register ['pub_topic' ]))
228+ raise ValueError ("Bad YAML configuration. pub_topic '{}' duplicated across registers with a "
229+ "duplicated json_key field. Registers that share a pub_topic must also have "
230+ "a unique json_key." .format (register ['pub_topic' ]))
210231 duplicate_json_keys [register ['pub_topic' ]] += [register ['json_key' ]]
211232 if 'retain' in register :
212233 retain_setting [register ['pub_topic' ]].add (register ['retain' ])
213234 # Check that there are no disagreements as to whether this pub_topic should be retained or not.
214235 for topic , retain_set in retain_setting .items ():
215236 if len (retain_set ) > 1 :
216- raise ValueError ("Bad YAML configuration. pub_topic '{}' has conflicting retain settings." .format (topic ))
237+ raise ValueError ("Bad YAML configuration. pub_topic '{}' has conflicting retain settings."
238+ .format (topic ))
217239
218240 def _load_modbus_config (self , path ):
219- yaml = YAML (typ = 'safe' )
220- result = yaml .load (open (path ,'r' ).read ())
241+ yaml = YAML (typ = 'safe' )
242+ result = yaml .load (open (path , 'r' ).read ())
221243 registers = [register for register in result ['registers' ] if 'pub_topic' in register ]
222244 mqtt_interface ._validate_registers (registers )
223245 return result
@@ -228,22 +250,41 @@ def loop_forever(self):
228250 self .poll ()
229251 sleep (self .config ['update_rate' ])
230252
253+
231254@click .command ()
232- @click .option ('--hostname' , default = 'localhost' , help = 'The hostname or IP address of the MQTT server.' , show_default = True )
233- @click .option ('--port' , default = 1883 , help = 'The port of the MQTT server.' , show_default = True )
234- @click .option ('--username' , default = 'username' , help = 'The username to authenticate to the MQTT server.' , show_default = True )
235- @click .option ('--password' , default = 'password' , help = 'The password to authenticate to the MQTT server.' , show_default = True )
236- @click .option ('--config' , default = './Sungrow_SH5k_20.yaml' , help = 'The YAML config file for your modbus device.' , show_default = True )
237- @click .option ('--mqtt_topic_prefix' , default = 'modbus4mqtt' , help = 'A prefix for published MQTT topics.' , show_default = True )
238- def main (hostname , port , username , password , config , mqtt_topic_prefix ):
255+ @click .option ('--hostname' , default = 'localhost' ,
256+ help = 'The hostname or IP address of the MQTT server.' , show_default = True )
257+ @click .option ('--port' , default = 1883 ,
258+ help = 'The port of the MQTT server.' , show_default = True )
259+ @click .option ('--username' , default = 'username' ,
260+ help = 'The username to authenticate to the MQTT server.' , show_default = True )
261+ @click .option ('--password' , default = 'password' ,
262+ help = 'The password to authenticate to the MQTT server.' , show_default = True )
263+ @click .option ('--mqtt_topic_prefix' , default = 'modbus4mqtt' ,
264+ help = 'A prefix for published MQTT topics.' , show_default = True )
265+ @click .option ('--config' , default = './Sungrow_SH5k_20.yaml' ,
266+ help = 'The YAML config file for your modbus device.' , show_default = True )
267+ @click .option ('--use_tls' , default = False ,
268+ help = 'Configure network encryption and authentication options. Enables SSL/TLS.' , show_default = True )
269+ @click .option ('--insecure' , default = True ,
270+ help = 'Do not check that the server certificate hostname matches the remote hostname.' , show_default = True )
271+ @click .option ('--cafile' , default = None ,
272+ help = 'The path to a file containing trusted CA certificates to enable encryption.' , show_default = True )
273+ @click .option ('--cert' , default = None ,
274+ help = 'Client certificate for authentication, if required by server.' , show_default = True )
275+ @click .option ('--key' , default = None ,
276+ help = 'Client private key for authentication, if required by server.' , show_default = True )
277+ def main (hostname , port , username , password , config , mqtt_topic_prefix , use_tls , insecure , cafile , cert , key ):
239278 logging .basicConfig (
240279 format = '%(asctime)s %(levelname)-8s %(message)s' ,
241280 level = logging .INFO ,
242281 datefmt = '%Y-%m-%d %H:%M:%S' )
243282 logging .info ("Starting modbus4mqtt v{}" .format (version .version ))
244- i = mqtt_interface (hostname , port , username , password , config , mqtt_topic_prefix )
283+ i = mqtt_interface (hostname , port , username , password , config , mqtt_topic_prefix ,
284+ use_tls , insecure , cafile , cert , key )
245285 i .connect ()
246286 i .loop_forever ()
247287
288+
248289if __name__ == '__main__' :
249290 main ()
0 commit comments