kizniche / Mycodo

An environmental monitoring and regulation system
http://kylegabriel.com/projects/
GNU General Public License v3.0
2.95k stars 494 forks source link

RS485/Modbus support? #1167

Open tomsepe opened 2 years ago

tomsepe commented 2 years ago

I am looking at some industrial humidity/temperature sensors that use the Modbus protocol (RS485) such as this: https://www.vaisala.com/en/products/instruments-sensors-and-other-measurement-devices/instruments-industrial-measurements/hmd110-hmw110

I am wondering if there is a way to integrate Modbus sensors as a custom input?

I am considering interfacing them by using an RS485 shield https://www.hwhardsoft.de/english/projects/rs485-arduino/ and then using the Arduino to convert the data to i2c.

What do you think about this approach?

Thank you!

kizniche commented 2 years ago

if there is a way to integrate Modbus sensors as a custom input?

I'm not sure. I've never worked with it, but it looks fairly straightforward. I'd be more inclined to try to interface directly to the Pi, rather than use an arduino, with only one intermediary IC, something like the following:

https://www.sparkfun.com/products/10124 https://www.sparkfun.com/products/9822 https://wiki.seeedstudio.com/RS-485_Shield_for_Raspberry_Pi/ https://www.amazon.com/RS485-CAN-HAT-Long-Distance-Communication/dp/B07VMB1ZKH

Josuah8 commented 2 years ago

Hi, you can use a cheap CH340/341 serial to USB converter stick and plug it into your pi, connect the modbus wires to it. In Mycodo just write a custom Python script to read the Modbus registers and return the values to Mycodo. Works like a charm. But integration like the other interface types would have saved me quite some time for sure.

Example:

import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('/dev/ttyUSB0', slaveaddress=1, close_port_after_each_call=True, debug=False)  # port name, slave address (in decimal)
#instrument.serial.port                     # this is the serial port name
instrument.serial.baudrate = 9600         # Baud
instrument.serial.bytesize = 8
instrument.serial.parity = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout = 1          # seconds
#print(instrument)
#instrument.address = 1                         # this is the slave address number
#instrument.serial.rtscts = True
#instrumentseral.xonxoff = True
#instrument.serial.dsrdtr = True
instrument.mode = minimalmodbus.MODE_RTU   # rtu or ascii mode
instrument.clear_buffers_before_each_transaction = True
## Read temperature (PV = ProcessValue) ##instrument.serial.port                     # this is the serial port name
#print(instrument)
ECtemp = instrument.read_register(registeraddress=2, number_of_decimals=0, functioncode=3) #read_long(1, 3)  # Registernumber, function code
ECtemp = ECtemp / 10

ECms = instrument.read_long(0, 3) / 100 #instrument.read_register(registeraddress=0, number_of_decimals=2, functioncode=3)

#ECppm = instrument.read_long(2, 3)

# Store measurements in database (must specify the channel and measurement)
self.store_measurement(channel=0, measurement=ECtemp)
self.store_measurement(channel=1, measurement=ECms)
#self.store_measurement(channel=2, measurement=ECppm)
lalebarde commented 2 years ago

There are also cheap UART-RS485 converters like the MAX485 module. I have just bought it, but I don't find out how to read sensors connected to the UART with mycodo. @Josuah8, is your code applicable in my case, possibly changing the tty? Could you please elaborate on "just write a custom Python script to read the Modbus registers and return the values to Mycodo"?

Josuah8 commented 2 years ago

Hi, yes, if you manage to set the max485 or whatever other solution up outside of mycodo then you know which tty to talk to. Then you can write a script to read the values. Once this works you only have to adapt the script to make it return values to mycodo as described in the manual section about custom python scripts as input module. That's it. The python script I posted works for my case, it uses minimalmodbus. The python library you want to use must be installed in the virtual environment of Mycodo, otherwise it won't find it. You should be able to modify my script to match your modbus settings and the register actions you want to perform. In my case I really got grey hair while trying to get it running outside of mycodo. I had one usb stick with CH340 chip that was able to read data from one sensor but not from another one and I was not able to figure out why. Until I tried a different CH340 stick, it worked immediately. Even without resistors and connecting the two lines to GND and voltage. The rest was easy because the script was already correct. I needed some testing with the data types to get meaningful data out of the sensor and also figured out that the documentation of the sensor was a bit misleading concerning the register addresses. But I got it up and running and it's working well since a year already. If we had a modbus interface in mycodo it would make it a lot easier to change settings on the fly without having to edit the python script.

hth Josuah

Laurent Alebarde @.***> schrieb am Di., 12. Apr. 2022, 13:46:

There are also cheap UART-RS485 converters like the MAX485 module https://medium.com/raspberry-pi-and-rs485-modbus/modbus-rs485-raspberry-pi-5ccbc1996b7d. I have just bought it, but I don't find out how to read sensors connected to the UART with mycodo. @Josuah8 https://github.com/Josuah8, is your code applicable in my case, possibly changing the tty? Could you please elaborate on "just write a custom Python script to read the Modbus registers and return the values to Mycodo"?

— Reply to this email directly, view it on GitHub https://github.com/kizniche/Mycodo/issues/1167#issuecomment-1096462773, or unsubscribe https://github.com/notifications/unsubscribe-auth/AYUEHM4K76R65ZVH3RIDNKTVEVA73ANCNFSM5SNXVALQ . You are receiving this because you commented.Message ID: @.***>

kizniche commented 2 years ago

This issue has been mentioned on Radical DIY Forum. There might be relevant details there:

https://forum.radicaldiy.com/t/custom-input-for-industrial-ec-with-rs485-protocol/751/2

friedpenguin commented 2 years ago

Haha. You pointed the forum back to here. MODBUS would be a cool addition because a lot of solar charge controllers use MODBUS as well. And the way you've integrated MQTT to send data to/from where needed is awesome. Great project! Getting some traction on Reddit too.

kizniche commented 1 year ago

This issue has been mentioned on Radical DIY Forum. There might be relevant details there:

https://forum.radicaldiy.com/t/brainstorming-par-photosynthetically-active-radiation-light-sensor-for-mycodo/1557/1

tazomatalax commented 6 months ago

I have solved it using minimalmodbus. Needed to add /dev/ttyUSB0 and i had my registers wrong as the master was starting the registers at 1 instead of 0.

The following code along with installing the libraries with sudo ~/Mycodo/env/bin/pip install minimalmodbus pyserial is all you need to get pH and temp from a hamilton arc pH sensor

import minimalmodbus
import serial
import struct

# Create an instrument object
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', slaveaddress=1, debug=False)
instrument.serial.baudrate = 19200
instrument.serial.bytesize = 8
instrument.serial.parity = serial.PARITY_NONE
instrument.serial.stopbits = 2
instrument.serial.timeout = 1
instrument.mode = minimalmodbus.MODE_RTU
instrument.clear_buffers_before_each_transaction = True

# Read 10 registers starting from 2090
ph_values = instrument.read_registers(2089, 10, functioncode=3)

# Read 10 registers starting from 2410
temp_values = instrument.read_registers(2409, 10, functioncode=3)

# Combine the register values to form 32-bit integers
ph_int = (ph_values[3] << 16) | ph_values[2]
temp_int = (temp_values[3] << 16) | temp_values[2]

# Convert the 32-bit integers to bytes
ph_bytes = ph_int.to_bytes(4, 'little')
temp_bytes = temp_int.to_bytes(4, 'little')

# Interpret the bytes as 32-bit floats
pH = struct.unpack('f', ph_bytes)[0]
temperature = struct.unpack('f', temp_bytes)[0]

# Store measurements
self.store_measurement(channel=0, measurement=round(pH, 2))
self.store_measurement(channel=1, measurement=round(temperature, 2))