Mimic Chint DDSU666 Meter with python - python

i am looking for some help.
i already figured out how to read the Chint DDSU666 energy meter, these meters are used with modbus RTU to communicate with solar battery inverters. so i would like to mimic this type of meter to adjust charging and discharging power.
my code to read this type of meter is:
import minimalmodbus
import struct
# The minimalmodbus library uses RTU mode for Modbus communication.
# You will need to set up the serial port and baud rate to match your energy meter's settings.
instrument1 = minimalmodbus.Instrument('com4',1)
instrument1.serial.baudrate = 9600
instrument1.close_port_after_each_call = True
# Set the slave address of your energy meter
instrument1.address = 0x01
while True:
try:
##Chint Meter DDSU666
spanning = instrument1.read_float(0x2000, 3, 2)
freq = instrument1.read_float(0x200e, 3, 2)
stroom = instrument1.read_float(0x2002, 3, 2)
vermogen = instrument1.read_float(0x2004, 3, 2)
baurdrate = instrument1.read_register(0x000c, 0)
print(spanning)
print(freq)
print(stroom)
print(vermogen)
print(baurdrate)
except:
print('communication lost')
now i want to write the registers above by my own data.
so i need to setup an modbus RTU slave/server where the inverter can read its data.
i started with pymodbus3.1 but was not able to get anywhere..
now i am trying with modbus_tk library to setup a server.
underneath my code.
import minimalmodbus
import struct
import pymodbus
import asyncio
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu
import serial
import time
modbusserver = modbus_rtu.RtuServer(serial.Serial('com4'),baudrate = 9600, bytesize=8,parity = 'N', stopbits=1, xonxoff=0)
print('start')
modbusserver.start()
slave1 = modbusserver.add_slave(1)
slave1.add_block('spanning',cst.HOLDING_REGISTERS,0x2000,250)
slave1.add_block('freq',cst.HOLDING_REGISTERS,0x200e,50)
slave1.add_block('vermogen',cst.HOLDING_REGISTERS,0x2002,3000)
could someone guide me what i need to use or what is the best option to be able to mimic this energymeter?
Thank you in advance.
tried above code to mimic the meter but not working.

Your code has two small problems.
First: you are not giving add_block the right values. According to this those are: name, type, starting address and number of registers so you should be doing something like:
slave1.add_block('spanning',cst.HOLDING_REGISTERS,0x2000,2)
slave1.add_block('freq',cst.HOLDING_REGISTERS,0x200e,2)
slave1.add_block('vermogen',cst.HOLDING_REGISTERS,0x2002,2)
And now that you have defined your blocks you can fill them with data. But be aware that to do that you need to use raw registers. You can't just send a float because the block is expecting raw registers as a list.
To circumvent that problem, the easiest thing to do is, instead of reading floats like so:
spanning = instrument1.read_float(0x2000, 3, 2)
Just read raw registers:
spanning = instrument1.read_registers(0x2000, 3, 2) #better read raw regs
freq = instrument1.read_registers(0x200e, 3, 2)
...
That should result in a list of two registers, so then you can directly do:
slave1.set_values("spanning", 0, spanning) #note that the first time we use quotes
#but not on the second. The fist is the
#name of the block and the second the
#list of values for the regs
...
Now you are ready to start your server with:
modbusserver.start() #start your server AFTER adding blocks and setting values
Finally, it might be a good idea to add a provision to manually stop your server; otherwise, you might end up killing the task resulting in havoc. You can have a look at this answer for inspiration.
Good luck! and veel plezier.

Related

Bind Bluetooth device programmatically to rfcomm via python in

i wrote a script in python for serial communication between my M5Stack Stick C (like raduino) and the raspberry pi.
all work fine. i can send "X","Y" or "Z" from raspberry py to the stick and he will reply the value (G-Force) back to the raspi! so far so good
Codes:
Python on raspy:
import serial
import time
import threading
ser = serial.Serial('/dev/rfcomm5') #init serial port
input_line = []#init input char array
def process_data(_data):
#called every time a sream is terminated by \n
#and the command string is ready to use
command = convert(_data)
print(command)
def convert(s): #convert the char list in a string
new = "" #init string to append all chars from char array
for x in s: # traverse in the string
new += str(x)
return new # return string
def processIncomingByte(inByte):#adding incoming chars to input_line
global input_line# globalize the input_line
if(inByte == '\n'):#if \n is incoming, end the chararray and release process data method
process_data(input_line)
input_line = []#reset input_line for next incoming string
elif(inByte == '\r'):
pass
else:#put all incoming chars in input_line
input_line.append(inByte)
while True:
while(ser.in_waiting > 0):#while some data is waiting to read....
processIncomingByte(ser.read())#.... process bytes whit method
ser.write(b'X\n')
time.sleep(0.5)
before the script work, i have to manually bind the m5Stak Stick-C over Blueman
to /dev/Rfcomm5. it work just fine over GUI or Console....
but now i would like to connect the stick via python to rfcomm5 (just by know the MAC adress, will be found in a config file later on...)
i startet to investigate a bit, but the more i research the more confused i am!!
i read some stuff over sockets and server-client aproaches. over a seperated script and so on....
i tested this code:
from bluetooth import *
target_name = "M5-Stick-C"
target_address = None
nearby_devices = discover_devices()
for address in nearby_devices:
if (target_name == lookup_name( address )):
target_address = address
break
if (target_address is not None):
print ("found target bluetooth device with address ", target_address)
else:
print ("could not find target bluetooth device nearby")
and indeed it found the device (just testing)!
but do i realy need to make a second script/process to connect to from my script?
is the the M5stack Stick-C the server? (i think so)
im so confused about all that stuff. i coded a lot, but never whit sockets, server-client stuff.
basically the communication (server/client?) works.
i just need to connect the device i found in the second script via macadress to rfcomm5 (or whatever rfcomm).
do i need a bluetooth socket? like in this example
https://gist.github.com/kevindoran/5428612
isnt the rfcomm the socket or am i wrong?
There are a number of layers that are used in the communication process and depending where you tap into that stack will depend what coding you need to do. The other complication is that BlueZ (the Bluetooth stack on linux) changed how it works over recent times leaving a lot of out of date information on the internet and easy for people to get confused.
With two Bluetooth devices, they need to establish a pairng. This is typically a one off provisioning step. This can be done with tools like Blueman or on the command line with bluetoothctl. Once you have a pairing established between your RPi and the M5Stack Stick, you shouldn't need to discover nearby devices again. Your script should just be able to connect if you tell it which device to connect to.
The M5Stack stick is advertising as having a Serial Port Profile (SPP). This is a layer on top of rfcomm.
There is a blog post about how this type of connection can be done with the standard Python3 installation: http://blog.kevindoran.co/bluetooth-programming-with-python-3/
My expectation is that you will only have to do the client.py on your RPi as the M5Stack Stick is the server. You will need to know its address and which port to connect on. Might be some trial and error on the port number (1 and 3 seem to be common).
Another library that I find helpful for SPP, is bluedot as it abstracts away some of the boilerplate code: https://bluedot.readthedocs.io/en/latest/btcommapi.html#bluetoothclient
So in summary, my recommendation is to use the standard Python Socket library or Bluedot. This will allow you to specify the address of the device you wish to connect to in your code and the underlying libraries will take care of making the connection and setting up the serial port (as long as you have already paired the two devices).
Example of what the above might look like with Bluedot
from bluedot.btcomm import BluetoothClient
from signal import pause
from time import sleep
# Callback to handle data
def data_received(data):
print(data)
sleep(0.5)
c.send("X\n")
# Make connection and establish serial connection
c = BluetoothClient("M5-Stick-C", data_received)
# Send initial requests
c.send("X\n")
# Cause the process to sleep until data received
pause()
Example using the Python socket library:
import socket
from time import sleep
# Device specific information
m5stick_addr = 'xx:xx:xx:xx:xx:xx'
port = 5 # This needs to match M5Stick setting
# Establish connection and setup serial communication
s = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM)
s.connect((m5stick_addr, port))
# Send and receive data
while True:
s.sendall(b'X\n')
data = s.recv(1024)
print(data)
sleep(0.5)
s.close()

Multiple reproducible errors with a Brecknell Scale

I'm working on getting a weight from a usb scale using python and PyUSB. Here's my code
import sys
import usb.core
import usb.util
from reports import \
ReportFactory, STATUSES, ZERO_WEIGHT, STABLE_WEIGHT, DATA_REPORT
device = usb.core.find(idVendor=0x67b, idProduct=0x2303)
if device is None:
raise ValueError('Device Not Found')
if device.is_kernel_driver_active(0) is True:
device.detach_kernel_driver(0)
usb.util.claim_interface(device, 0)
device.set_configuration()
collected = 0
attempts = 50
while collected < attempts:
ret = device.read(0x2,0x0040)
sret=''.join([chr(x) for x in ret])
print "Return Raw: ",ret
print "Return : ", sret
print ReportFactory.build(ret)
# release the device
usb.util.release_interface(device, 0)
device.attach_kernel_driver(0)
The first time I run this program I get Resource busy error
The second run I get Operation timed out on the 5th dump.
Subsequent runs after that result in immediate timeouts.
There are several things going on here
Why do I get a resource busy error on first run? I would think the kernel_driver if statement I have should take care of it.
Why do I get data that looks nothing like what a USB scale should produce?
Why the timeout after 4 dumps of data? Everytime!
The device information is thus
Any help would be much appreciated!
Ok, anyone that is struggling to get anything from the GP100 Brecknell scale using PyUSB, switch to PySerial. Since this is one of the cheapest USB scales on the market, I'm guessing this information will help many.
I emailed the manufacturer for documentation and they've sent me some valuable serial protocol information.
If you set the protocol of the scale to 7010 you can use python code that looks like this
import time
import serial
ser = serial.Serial(
port='/dev/ttyUSB0',
baudrate=2400,
bytesize=8,
parity='N',
stopbits=2
)
while 1:
x = ser.read(100)
print(x)
This will stream 8 bytes of data.
The first byte is status information that can be interpreted thusly
Bits 2-4
Bits 5-8
Bytes 2 & 3 can be safely ignored.
Bytes 4-8 have the weight information, represented by characters 0-9 (e.g. 00023 = 2.3 lbs). This scale is only accurate +- 0.6 lbs and does not work in ounces, perhaps other models using the same serial controller utilize the oz capabilities. The PG100 does not.

ser.inWaiting() always returns 0 when reading a virtual port

I'm having difficulties getting pyserial to play nicely with a virtual port. I know this is an area which a few others have written about, but I couldn't find anything which solved my problem in those answers. Forgive me if I'm just being dense, and the solution exists ready-made elsewhere.
This is what I'm trying to achieve: I want to set up a virtual port, to which I can write data in one .py file, and from which I can then read data in another .py file. This is for the purposes of development and testing; I don't always have access to the device around which my current project is built.
This is my code so far:
dummy_serial.py
import os, pty, serial, time
master, slave = pty.openpty()
m_name = os.ttyname(master)
s_name = os.ttyname(slave)
# This tells us which ports "openpty" has happened to choose.
print("master: "+m_name)
print("slave: "+s_name)
ser = serial.Serial(s_name, 9600)
message = "Hello, world!"
encoded = message.encode("ascii")
while True:
ser.write(encoded)
time.sleep(1)
reader.py
import serial, time
# The port will change, depending on what port "openpty" (in the other file)
# happens to choose.
ser = serial.Serial("/dev/pts/1", 9600)
while True:
time.sleep(1)
incoming_bytes = ser.inWaiting()
# This print statement gives us an idea of what's going on.
print(incoming_bytes)
if incoming_bytes != 0:
data = ser.read(incoming_bytes)
print(data)
At present, dummy_serial.py seems to run okay. However, reader.py just keeps saying that there are no bytes waiting to be read, and hence reads no data.
What I would like:
An explanation of why ser.inWaiting() keeps returning 0, and a solution which makes ser.read(x) actually spit out "Hello, world!"
Or an explanation of why what I'm trying to do is fundamentally silly, and a better means of creating a writeable/readable virtual port.

Can't communicate with Modbus device using minimalmodbus

I am failing to communicate with a device using minimalmodbus, Modbus RTU.
I have connected the device to a raspberry pi via a usb to rs-485 adapter (FTDI chip) A to A and B to B.
The device configurations are as follows:
Port settings:
Baud rate: 9600
Parity: No
Stop bits: 1
Silent Interval: 70 ms.
Transmission Mode: RTU
Interface 2 Wire RS485
Connector of ECM: DB9
Connections: A on pins 1, 4. B on pins 6, 9
Modbus Holding registers (simplified for the purpose of this example)
Swapped Floating point format:
40001 (address 0) Parameter 1
40003 (address 2) Parameter 2
I have tried using raw serial commands to communicate with the device, as well as using pymodbus, and now on my latest attempt minimal modbus. Please see below my code attempt using minimalmodbus.
Also, i know the serial adapter works as i use it for various other serial sensors. Only difference is that i am working with modbus now.
import minimalmodbus
instrument = minimalmodbus.Instrument('/dev/tty/USB1',1)
instrument.debug = True
instrument.handle_local_echo = True # The serial device echos back every write, hence this
instrument.serial.baudrate = 9600
instrument.serial.timeout = 1
value = instrument.read_register(0,2)
print value
I expected to receive some sort of reply even if an error or incorrect, but i am getting nothing. the debug output of minimalmodbus says "IOError: No communication with the instrument(no answer)"
Is there any obvious mistakes in my code vs the sensor requriements? I confirmed my wiring with the manufacturer, and is correct.
Quoting from the manual of your cable:
The USB-RS485-WE cable allows for local echo to be enabled/disabled by changing a bit in the FT232R
EEPROM. If CBUS4 in the EEPROM is set for “PWRON#” local echo is enabled. If CBUS4 in the EEPROM is
set for “TXDEN” local echo is disabled.
Users can set this with MPROG from www.ftdichip.com
The default for the local echo is disabled (CBUS4 set for “TXDEN)
Phew! lots of info in there. According to the thread of comments on your question you activated the software echo handling on minimalModbus because otherwise your routine would not wait for the response from the device on the other end. That makes one think whether your cable has the local echo enabled or disabled.
Fortunately, you can check that very easily. As the manual says just go get MPROG here. Extract and run (yeap, you need Windows for this tool, but you can run it on a Virtual Machine).
Connect your cable to the USB port (don't forget to send the device to the virtual machine if you are running one) and select Tools-->Read and Parse on MPROG.
This is what you should get:
Make sure you have TXEN selected on box C4. According to the manual, you should have TXEN by default, if you see PWRON# it means the local echo is active. Disable it and you should be good to go to use Modbus.
I don't see an obvious error from your side. That's rather difficult since you are working with hardware.
I'll provide some of my code. I used it for a prototype, which was a Raspberry Pi 3B with a USB to RS485 converter (This one).
from modbus import minimalmodbus
import serial
import time
from mqtt.client import Client as mqtt_client
class Slave:
def __init__(self, serial_port: str = '/dev/ttyUSB0', slave_id: int = 5,
baudrate: int = 38400, byte_size: int = 8,
parity: str = serial.PARITY_NONE, stopbits: int = 1,
timeout: float = 1.0):
self.slave = minimalmodbus.Instrument(serial_port, slave_id)
self.slave.serial.baudrate = baudrate
self.slave.serial.bytesize = byte_size
self.slave.serial.parity = parity
self.slave.serial.stopbits = stopbits
self.slave.serial.timeout = timeout
self.registers = ["header", "zero", "life_beat",
"test_int", "test_float"]
self.output = mqtt_client()
...
When I read a register I used e.g.:
self.slave.read_register(2)
//or
self.slave.read_float(5)
I'm not sure which python version I used. I think it was 3.6.x.

Convert RS232 Ascii to Modbus TCP using Pymodbus

I am trying to convert RS252 Ascii string data from a sensor to Modbus TCP Input/Holding registers using pymodbus Callback Sever, the server is the master reporting data when requested to a client logger, and I am not sure what I need to do to get this to work. I am currently able to read the data and log it to a csv file using this
#!/usr/bin/env python
# Log data from serial port
import argparse
import serial
import datetime
import time
import os
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0")
parser.add_argument("-s", "--speed", help="speed in bps", default=9600, type=int)
args = parser.parse_args()
outputFilePath = os.path.join(os.path.dirname(__file__),
datetime.datetime.now().strftime("%Y-%m-%d") + ".csv")
with serial.Serial(args.device, args.speed) as ser, open(outputFilePath,'w') as outputFile:
print("Logging started. Ctrl-C to stop.")
try:
while True:
time.sleep(0.2)
x = (ser.read(ser.inWaiting()))
data = x.decode('UTF-8')
if data !="":
outputFile.write(time.strftime("%Y/%m/%d %H:%M ") + " " + data )
outputFile.flush()
except KeyboardInterrupt:
print("Logging stopped")
The string from the sensor comes out of the device as:
0.00 0.0 0.0 346.70 25.14
I need to have each piece as its own Modbus register and I am trying to use pymodbus on a Raspberry Pi Zero. The sensor updates 4 times a second and I am able to break the data into parts, I just haven;t gotten to that yet because I am not sure what I need to do in the Callback script, I am not that versed in Python yet I am I am still learning. I do have an understanding of Modbus TCP and have used it before on Arduino systems. Any help would be appreciated.
What you need is updating server, which you could use to populate the registers . You will have to focus on function def updating_writer and do the serial reads, process them and write to registers of your choice. The example is hard to read and understand in first go. I have modified the example to meet your needs. But here are some key concepts which will be handy to understand the code.
ModbusSlaveContext
BinaryPayloadBuilder
Also note, the example uses asynchronous server based on twisted, If you are new to twisted or have some constraints which will not allow you to use twisted on your target, you can achieve the same with simple threads as well. The design would be roughly like this.
Start your updating function in a separate thread
Start your TCP server at the end (blocking)
# Complete gist here --> https://gist.github.com/dhoomakethu/540b15781c62de6d1f7c318c3fc8ae22
def updating_writer(context, device, baudrate):
""" A worker process that runs every so often and
updates live values of the context. It should be noted
that there is a race condition for the update.
:param arguments: The input arguments to the call
"""
log.debug("updating the context")
log.debug("device - {}, baudrate-{}".format(device, baudrate))
data = serial_reader(device, baudrate) # Implement your serial reads, return list of floats.
if data:
# We can not directly write float values to registers, Use BinaryPayloadBuilder to convert float to IEEE-754 hex integer
for d in data:
builder.add_32bit_float(d)
registers = builder.to_registers()
context = context
register = 3 # Modbus function code (3) read holding registers. Just to uniquely identify what we are reading from /writing in to.
slave_id = 0x01 # Device Unit address , refer ModbusSlaveContext below
address = 0x00 # starting offset of register to write (0 --> 40001)
log.debug("new values: " + str(registers))
context[slave_id].setValues(register, address, registers)
Once the server is running and the values are being updated, you can use a client to read values and parse it back to float.
from pymodbus.client.sync import ModbusTcpClient as Client
from pymodbus.payload import BinaryPayloadDecoder, Endian
client = Client(<IP-ADDRESS>, port=5020)
# Each 32 bit float is stored in 2 words, so we will read 10 registers
raw_values = client.read_holding_registers(0, 10, unit=1)
if not registers.isError():
registers = raw_values.registers
decoder = BinaryPayloadDecoder.fromRegisters(registers,
wordorder=Endian.Big, byteorder=Endian.Big)
for _ in range(5):
print(decoder.decode_32bit_float())

Categories

Resources