Alpha version of ModBus-MQTT and MQTT-APRS software.
This commit is contained in:
@@ -6,6 +6,7 @@ global:
|
|||||||
#program-log: /home/marcel/rs458.log # All program output will be written to this file (0 = do not log to file)
|
#program-log: /home/marcel/rs458.log # All program output will be written to this file (0 = do not log to file)
|
||||||
mqtt-server: mqtt.meezenest.nl
|
mqtt-server: mqtt.meezenest.nl
|
||||||
mqtt-port: 1883
|
mqtt-port: 1883
|
||||||
|
telemetry-interval: 10 # number of seconds between transmissions
|
||||||
|
|
||||||
# APRS settings
|
# APRS settings
|
||||||
aprs:
|
aprs:
|
||||||
@@ -14,12 +15,10 @@ aprs:
|
|||||||
to_call: PE1RXF-1 # Call of the receiver
|
to_call: PE1RXF-1 # Call of the receiver
|
||||||
destination: APZMDM # APRS destination
|
destination: APZMDM # APRS destination
|
||||||
digipath: 0 # Digipeater path for weather reports (0 = no path)
|
digipath: 0 # Digipeater path for weather reports (0 = no path)
|
||||||
interval: 30
|
|
||||||
# - port: ax1 # Linux AX.25 port to which APRS weather report is sent
|
# - port: ax1 # Linux AX.25 port to which APRS weather report is sent
|
||||||
# call: PE1RXF-13 # Call from which transmissions are made (can be a different call from the call assigned to the AX.25 port)
|
# call: PE1RXF-13 # Call from which transmissions are made (can be a different call from the call assigned to the AX.25 port)
|
||||||
# destination: APZMDM # APRS destination
|
# destination: APZMDM # APRS destination
|
||||||
# digipath: WIDE2-1 # Digipeater path for weather reports (0 = no path)
|
# digipath: WIDE2-1 # Digipeater path for weather reports (0 = no path)
|
||||||
# interval: 30
|
|
||||||
|
|
||||||
# Define the MQTT data to transmit over AX25. The order of the items is the order the data is combined to the telemetry string.
|
# Define the MQTT data to transmit over AX25. The order of the items is the order the data is combined to the telemetry string.
|
||||||
mqtt:
|
mqtt:
|
||||||
|
@@ -58,6 +58,7 @@ class config_reader:
|
|||||||
tmp = self.config_file_settings['global']['program-log']
|
tmp = self.config_file_settings['global']['program-log']
|
||||||
tmp = self.config_file_settings['global']['mqtt-server']
|
tmp = self.config_file_settings['global']['mqtt-server']
|
||||||
tmp = self.config_file_settings['global']['mqtt-port']
|
tmp = self.config_file_settings['global']['mqtt-port']
|
||||||
|
tmp = self.config_file_settings['global']['telemetry-interval']
|
||||||
except:
|
except:
|
||||||
print ("Error in the global section of the configuration file.")
|
print ("Error in the global section of the configuration file.")
|
||||||
return 0
|
return 0
|
||||||
@@ -72,7 +73,6 @@ class config_reader:
|
|||||||
tmp = entry['to_call']
|
tmp = entry['to_call']
|
||||||
tmp = entry['destination']
|
tmp = entry['destination']
|
||||||
tmp = entry['digipath']
|
tmp = entry['digipath']
|
||||||
tmp = entry['interval']
|
|
||||||
except:
|
except:
|
||||||
print ("Error in the aprs section of the configuration file.")
|
print ("Error in the aprs section of the configuration file.")
|
||||||
return 0
|
return 0
|
||||||
|
@@ -43,7 +43,7 @@ class MqttHandler:
|
|||||||
self.config = config
|
self.config = config
|
||||||
# Define list with length equal to the number of subscriptions defined in the config file
|
# Define list with length equal to the number of subscriptions defined in the config file
|
||||||
self.number_of_mqtt_subscriptions = len(config['mqtt']['subscribe'])
|
self.number_of_mqtt_subscriptions = len(config['mqtt']['subscribe'])
|
||||||
self.aprs_telemetry_data = ['0.0']*self.number_of_mqtt_subscriptions
|
self.aprs_telemetry_data = [bytes(b'0.0')]*self.number_of_mqtt_subscriptions
|
||||||
|
|
||||||
def process_message(self, client, userdata, message):
|
def process_message(self, client, userdata, message):
|
||||||
|
|
||||||
@@ -191,16 +191,13 @@ def send_data_to_aprs(weather_data, configuration):
|
|||||||
logging.error("Failed to send data to APRS radio.")
|
logging.error("Failed to send data to APRS radio.")
|
||||||
logging.error(f"Command returned: {e}")
|
logging.error(f"Command returned: {e}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
Configuration, MqttClient, mqtt_handler = setup()
|
Configuration, MqttClient, mqtt_handler = setup()
|
||||||
LoopCounter = 0
|
LoopCounter = 0
|
||||||
|
|
||||||
while (1):
|
while (1):
|
||||||
time.sleep(3) # Sleep for 3 seconds
|
time.sleep(Configuration.config_file_settings['global']['telemetry-interval']) # Sleep for number of seconds set in config.yaml
|
||||||
|
|
||||||
# Send APRS telemetry every 10 cycles = every 10 minutes
|
|
||||||
LoopCounter = LoopCounter + 1
|
|
||||||
if LoopCounter >= 1:
|
|
||||||
|
|
||||||
# Send data to LoRa radio via external program (/usr/sbin/beacon). Make sure we use all radios defined in the configuration file.
|
# Send data to LoRa radio via external program (/usr/sbin/beacon). Make sure we use all radios defined in the configuration file.
|
||||||
for entry in Configuration.config_file_settings['aprs']:
|
for entry in Configuration.config_file_settings['aprs']:
|
||||||
@@ -209,7 +206,6 @@ while (1):
|
|||||||
# We cannot send multiple APRS messages in a short period of time, so we wait 3 deconds between messages.
|
# We cannot send multiple APRS messages in a short period of time, so we wait 3 deconds between messages.
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
LoopCounter = 0
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ global:
|
|||||||
modbus:
|
modbus:
|
||||||
#port: /dev/serial/by-path/platform-3f980000.usb-usb-0:1.3:1.0-port0 # USB port to which RS-485 dongle is connected
|
#port: /dev/serial/by-path/platform-3f980000.usb-usb-0:1.3:1.0-port0 # USB port to which RS-485 dongle is connected
|
||||||
port: /dev/ttyUSB0
|
port: /dev/ttyUSB0
|
||||||
|
poll_speed: 60 # Polling speed in seconds
|
||||||
|
|
||||||
# Modbus servers settings
|
# Modbus servers settings
|
||||||
modbus_servers:
|
modbus_servers:
|
||||||
|
@@ -70,6 +70,7 @@ class config_reader:
|
|||||||
# Test is all expected settings are present
|
# Test is all expected settings are present
|
||||||
try:
|
try:
|
||||||
tmp = self.config_file_settings['modbus']['port']
|
tmp = self.config_file_settings['modbus']['port']
|
||||||
|
tmp = self.config_file_settings['modbus']['poll_speed']
|
||||||
except:
|
except:
|
||||||
print ("Error in the modbus section of the configuration file.")
|
print ("Error in the modbus section of the configuration file.")
|
||||||
return 0
|
return 0
|
||||||
|
@@ -175,7 +175,7 @@ def data_logger(data, configuration):
|
|||||||
except:
|
except:
|
||||||
logging.warning("Could not write to file: " + new_filename)
|
logging.warning("Could not write to file: " + new_filename)
|
||||||
|
|
||||||
def send_data_to_mqtt(data, configuration, modbus_registers, mqtt_client):
|
def send_data_to_mqtt(data, configuration, modbus_registers, MqttClient):
|
||||||
|
|
||||||
mqtt_top_topic = []
|
mqtt_top_topic = []
|
||||||
mqtt_full_topic = []
|
mqtt_full_topic = []
|
||||||
@@ -208,8 +208,7 @@ def send_data_to_mqtt(data, configuration, modbus_registers, mqtt_client):
|
|||||||
# Go through every input register and match the unit and description with the value
|
# Go through every input register and match the unit and description with the value
|
||||||
for index2, entry2 in enumerate(data['InputRegisters']):
|
for index2, entry2 in enumerate(data['InputRegisters']):
|
||||||
# Scale values
|
# Scale values
|
||||||
entry2 = entry2/ (10^entry1['input_register_names'][index2][2])
|
entry2 = entry2/ pow(10, entry1['input_register_names'][index2][2])
|
||||||
|
|
||||||
message_topic = entry1['input_register_names'][index2][0]
|
message_topic = entry1['input_register_names'][index2][0]
|
||||||
mqtt_message = str(round(entry2,1))
|
mqtt_message = str(round(entry2,1))
|
||||||
mqtt_full_topic = mqtt_top_topic + message_topic
|
mqtt_full_topic = mqtt_top_topic + message_topic
|
||||||
@@ -234,68 +233,71 @@ def ReconnectModBus(configuration):
|
|||||||
|
|
||||||
logging.info("Reconnected to ModBus dongle.")
|
logging.info("Reconnected to ModBus dongle.")
|
||||||
|
|
||||||
Configuration, Controller, ModbusAddresses, ModbusRegisters, MqttClient = setup()
|
def main():
|
||||||
LoopCounter = 0
|
|
||||||
|
|
||||||
while (1):
|
Configuration, Controller, ModbusAddresses, ModbusRegisters, MqttClient = setup()
|
||||||
time.sleep(3) # Sleep for 3 seconds
|
LoopCounter = 0
|
||||||
|
|
||||||
ModBusData={}
|
while (1):
|
||||||
|
time.sleep(Configuration.config_file_settings['modbus']['poll_speed']) # Sleep for number of seconds given in config.yaml
|
||||||
|
|
||||||
# Loop through all configured ModBus devices and try to read the sensor data
|
ModBusData={}
|
||||||
for index, current_modbus_device in enumerate(ModbusAddresses):
|
|
||||||
logging.debug("Reading device on ModBus address: " + str(current_modbus_device))
|
|
||||||
|
|
||||||
try:
|
# Loop through all configured ModBus devices and try to read the sensor data
|
||||||
ModBusData['DateTime'] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
for index, current_modbus_device in enumerate(ModbusAddresses):
|
||||||
ModBusData['ID'] = Controller[index].get_id()
|
logging.debug("Reading device on ModBus address: " + str(current_modbus_device))
|
||||||
if ModBusData['ID'][0] == 0x4D45:
|
|
||||||
ModBusData['Type'] = Controller[index].get_type()
|
|
||||||
ModBusData['TypeString'] = Controller[index].get_type_string()
|
|
||||||
ModBusData['InputRegisters'] = Controller[index].read_all_input_registers()
|
|
||||||
|
|
||||||
# Keep the watchdog from resetting the ModBusdevice
|
|
||||||
Controller[index].toggle_watchdog()
|
|
||||||
|
|
||||||
NoError = True
|
|
||||||
except minimalmodbus.NoResponseError:
|
|
||||||
# Handle communication timeout
|
|
||||||
logging.warning("No response from the instrument on ModBus address: " + str(current_modbus_device))
|
|
||||||
NoError = False
|
|
||||||
except minimalmodbus.InvalidResponseError:
|
|
||||||
# Handle invalid Modbus responses
|
|
||||||
logging.warning("Invalid response from the instrument on ModBus address: " + str(current_modbus_device))
|
|
||||||
NoError = False
|
|
||||||
except minimalmodbus.SlaveReportedException as e:
|
|
||||||
# Handle errors reported by the slave device
|
|
||||||
logging.error(f"The instrument reported an error: {e}")
|
|
||||||
NoError = False
|
|
||||||
except minimalmodbus.ModbusException as e:
|
|
||||||
# Handle all other Modbus-related errors
|
|
||||||
logging.error(f"Modbus error: {e}")
|
|
||||||
NoError = False
|
|
||||||
except Exception as e:
|
|
||||||
# Handle unexpected errors, probably I/O error in USB/serial
|
|
||||||
logging.error(f"Unexpected error: {e}" + ", serial port " + Configuration.config_file_settings['modbus']['port'] + " not available while reading device on ModBus address " + str(ModbusAddresses[index]) + ". Try to reconnect.")
|
|
||||||
Controller[index].serial.close()
|
|
||||||
ReconnectModBus(Configuration)
|
|
||||||
NoError = False
|
|
||||||
|
|
||||||
if NoError == True:
|
|
||||||
try:
|
try:
|
||||||
logging.debug("Found Mees Electronics sensor on ModBus address " + str(current_modbus_device))
|
ModBusData['DateTime'] = datetime.datetime.now().replace(microsecond=0).isoformat()
|
||||||
logging.debug("Serial number: " + hex(ModBusData['ID'][1]) + " " + hex(ModBusData['ID'][2]) + " " + hex(ModBusData['ID'][3]))
|
ModBusData['ID'] = Controller[index].get_id()
|
||||||
logging.debug("Device type: " + str(ModBusData['Type']) + " (" + ModBusData['TypeString'] + ")")
|
if ModBusData['ID'][0] == 0x4D45:
|
||||||
logging.debug (ModBusData['InputRegisters'])
|
ModBusData['Type'] = Controller[index].get_type()
|
||||||
#logging.debug (json.dumps(ModBusData, indent=1, sort_keys=False))
|
ModBusData['TypeString'] = Controller[index].get_type_string()
|
||||||
|
ModBusData['InputRegisters'] = Controller[index].read_all_input_registers()
|
||||||
|
|
||||||
except:
|
# Keep the watchdog from resetting the ModBusdevice
|
||||||
logging.warning("Modbus device type " + str(ModBusData['Type']) + " not found in register definition file. Ignoring sensor data.")
|
Controller[index].toggle_watchdog()
|
||||||
|
|
||||||
# Log sensor data to file if enabled in configuration file
|
NoError = True
|
||||||
data_logger(ModBusData, Configuration)
|
except minimalmodbus.NoResponseError:
|
||||||
|
# Handle communication timeout
|
||||||
|
logging.warning("No response from the instrument on ModBus address: " + str(current_modbus_device))
|
||||||
|
NoError = False
|
||||||
|
except minimalmodbus.InvalidResponseError:
|
||||||
|
# Handle invalid Modbus responses
|
||||||
|
logging.warning("Invalid response from the instrument on ModBus address: " + str(current_modbus_device))
|
||||||
|
NoError = False
|
||||||
|
except minimalmodbus.SlaveReportedException as e:
|
||||||
|
# Handle errors reported by the slave device
|
||||||
|
logging.error(f"The instrument reported an error: {e}")
|
||||||
|
NoError = False
|
||||||
|
except minimalmodbus.ModbusException as e:
|
||||||
|
# Handle all other Modbus-related errors
|
||||||
|
logging.error(f"Modbus error: {e}")
|
||||||
|
NoError = False
|
||||||
|
except Exception as e:
|
||||||
|
# Handle unexpected errors, probably I/O error in USB/serial
|
||||||
|
logging.error(f"Unexpected error: {e}" + ", serial port " + Configuration.config_file_settings['modbus']['port'] + " not available while reading device on ModBus address " + str(ModbusAddresses[index]) + ". Try to reconnect.")
|
||||||
|
Controller[index].serial.close()
|
||||||
|
ReconnectModBus(Configuration)
|
||||||
|
NoError = False
|
||||||
|
|
||||||
# Send sensor data to MQTT broker
|
if NoError == True:
|
||||||
send_data_to_mqtt(ModBusData, Configuration, ModbusRegisters.definition_file_data, MqttClient)
|
try:
|
||||||
|
logging.debug("Found Mees Electronics sensor on ModBus address " + str(current_modbus_device))
|
||||||
|
logging.debug("Serial number: " + hex(ModBusData['ID'][1]) + " " + hex(ModBusData['ID'][2]) + " " + hex(ModBusData['ID'][3]))
|
||||||
|
logging.debug("Device type: " + str(ModBusData['Type']) + " (" + ModBusData['TypeString'] + ")")
|
||||||
|
#logging.debug (ModBusData['InputRegisters'])
|
||||||
|
#logging.debug (json.dumps(ModBusData, indent=1, sort_keys=False))
|
||||||
|
|
||||||
|
except:
|
||||||
|
logging.warning("Modbus device type " + str(ModBusData['Type']) + " not found in register definition file. Ignoring sensor data.")
|
||||||
|
|
||||||
|
# Log sensor data to file if enabled in configuration file
|
||||||
|
data_logger(ModBusData, Configuration)
|
||||||
|
|
||||||
|
# Send sensor data to MQTT broker
|
||||||
|
send_data_to_mqtt(ModBusData, Configuration, ModbusRegisters.definition_file_data, MqttClient)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
Reference in New Issue
Block a user