Вход

Просмотр полной версии : СТГ 3 и modbus



atomo2
09.09.2024, 12:28
Пытаюсь считать с СТГ 3 текущие измеренное значение, 7853078531


Пример запроса данных:
01; 03; 00; 00; 00; 02; sum 0; sum 1,
где N номер сигнализатора в сети. Устанавливается пользователем в
диапазоне от 1 до 31;
sum 0; sum 1 контрольная сумма (CRC), рассчитывается в соответствии
с протоколом «MODBUS-RTU».


# Извлечение данных из регистров
reg0 = registers[0]
reg1 = registers[1]

# Разбиение регистров на байты
byte1 = (reg0 >> 8) & 0xFF
byte2 = reg0 & 0xFF
byte3 = (reg1 >> 8) & 0xFF
byte4 = reg1 & 0xFF

# Извлечение состояния сигнализации и знака
sign_bit = (byte1 >> 7) & 0x01
p1 = (byte1 >> 3) & 0x01
p2 = (byte1 >> 4) & 0x01
comma_position = (byte1 & 0x07)

# Определение знака
sign = '-' if sign_bit else '+'

# Чтение значений
integer_part = byte2 * 100 + byte3 * 10 + (byte4 >> 4)
decimal_part = (byte4 & 0x0F) * 100

# Формирование значения
value = integer_part + decimal_part / 10000
if sign_bit:
value = -value

# Сдвиг запятой
value /= 10 ** comma_position

# Форматирование результата
print(f"Значение: {value:.{comma_position}f}")
print(f"Состояние сигнализации: П1={p1}, П2={p2}")

def main():
# Параметры запроса
unit_id = 1
address = 0x00 # Начальный адрес
count = 2 # Количество регистров

while True:
# Чтение данных
data = read_modbus_data(client, unit_id, address, count)
if data:
interpret_data(data.registers)




Ну как бы все просто и элементарно;) но в ответ я получаю всякую ерунду

Значение: 0.02120
Состояние сигнализации: П1=0, П2=0
Значение: 0.02423
Состояние сигнализации: П1=0, П2=0
Значение: 0.01677
Состояние сигнализации: П1=0, П2=0
Значение: 0.015674
Состояние сигнализации: П1=0, П2=0
Значение: 0.016387


Хотя в официальной программе значение 78532 что я делаю не так?

atomo2
09.09.2024, 12:46
Все код рабочий, ошибка была тут integer_part = byte2 * 10000 + byte3 * 10 + (byte4 >> 4) на 10000 умножать надо было

atomo2
10.09.2024, 17:38
А вот пороги не получается считать , если я устанавливаю пороги 20 и 40 , то у меня считывается 32 и 64


Параметры запроса
unit_id = 1
address = 0x00 # Начальный адрес для измеренного значения
count = 2 # Количество регистров для измеренного значения
threshold1_address = 0x02 # Адрес порога 1
threshold2_address = 0x04 # Адрес порога 2
while True:
# Чтение данных измеренного значения
data = read_modbus_data(client, unit_id, address, count)
if data:
print("Текущее значение концентрации:")
interpret_data(data.registers)

# Чтение порога 1
p1_data = read_modbus_data(client, unit_id, threshold1_address, count)
if p1_data:
print("Установленное значение порога 1:")
interpret_data(p1_data.registers)
# Чтение порога 2
p2_data = read_modbus_data(client, unit_id, threshold2_address, count)
if p2_data:
print("Установленное значение порога 2:")
interpret_data(p2_data.registers)

1exan
10.09.2024, 18:15
А вот пороги не получается считать , если я устанавливаю пороги 20 и 40 , то у меня считывается 32 и 64
...


Примечательно, что это одинаковые значения, только в шестнадцатеричной и десятичной системе

atomo2
11.09.2024, 03:39
Примечательно, что это одинаковые значения, только в шестнадцатеричной и десятичной системе

Так то оно так) Но ведь у меня выводится то в десятичной системе , значит в 16 ричной что то другое считывается?


Откинем все лишние и сделаем только чтение 2х порогов

from pymodbus.client.sync import ModbusSerialClient as ModbusClient

# Конфигурация клиента Modbus
client = ModbusClient(
method='rtu', # Протокол RTU
port='/dev/ttyS6', # COM-порт (например, /dev/ttyS6 для Linux или COM6 для Windows)
baudrate=9600, # Скорость передачи данных
stopbits=1, # Стоп-биты
parity='N', # Четность (None)
bytesize=8 # Размер байта
)

# Подключение к клиенту
client.connect()

# Чтение значений порога 1 и порога 2
# Чтение регистров 0002 и 0003 для порога 1
result_threshold_1 = client.read_holding_registers(0x0002, 2, unit=1)
threshold_1 = result_threshold_1.registers

# Чтение регистров 0004 и 0005 для порога 2
result_threshold_2 = client.read_holding_registers(0x0004, 2, unit=1)
threshold_2 = result_threshold_2.registers

# Закрытие соединения
client.close()

# Вывод значений порогов
print(f"Установленное значение порога 1: {threshold_1[0]}, {threshold_1[1]}")
print(f"Установленное значение порога 2: {threshold_2[0]}, {threshold_2[1]}")

И получаем

Установленное значение порога 1: 1056, 0
Установленное значение порога 2: 1088, 0


Разобрался , нужно было выводить младший байт , позже подробнее напишу

atomo2
11.09.2024, 06:26
Изменил код считываю в 16 ричной системе

from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import time

# Параметры подключения
port = 'COM6'
baudrate = 9600
client = ModbusClient(
method='rtu',
port=port,
baudrate=baudrate,
stopbits=1,
parity='N',
timeout=1
)

def read_modbus_data(client, unit_id, address, count):
# Подключение к клиенту
if not client.connect():
print("Не удалось подключиться к Modbus серверу")
return None

# Чтение данных
result = client.read_holding_registers(address, count, unit=unit_id)
if result.isError():
print("Ошибка чтения данных: ", result)
return None

# Закрытие соединения
client.close()
return result

def interpret_data(registers, is_threshold=False):
if len(registers) < 2:
print("Недостаточно данных для интерпретации")
return

# Инициализация переменной value
value = None

# Извлечение данных из регистров
reg0 = registers[0]
reg1 = registers[1]

if is_threshold:
# Для порога младший и старший байты используются напрямую
byte1 = reg0 & 0xFF
byte2 = (reg0 >> 8) & 0xFF
byte3 = reg1 & 0xFF
byte4 = (reg1 >> 8) & 0xFF

# Интерпретация данных
hex_value = f"{reg0:04X} {reg1:04X}"
print(f"Значение порога (16-ричное): {hex_value}")

else:
# Разбиение регистров на байты
byte1 = (reg0 >> 8) & 0xFF
byte2 = reg0 & 0xFF
byte3 = (reg1 >> 8) & 0xFF
byte4 = reg1 & 0xFF

# Извлечение состояния сигнализации и знака
sign_bit = (byte1 >> 7) & 0x01
comma_position = (byte1 & 0x07)

# Определение знака
sign = '-' if sign_bit else '+'

# Чтение значений
integer_part = byte2 * 10000 + byte3 * 10 + (byte4 >> 4)
decimal_part = (byte4 & 0x0F) * 100

# Формирование значения
value = integer_part + decimal_part / 10000
if sign_bit:
value = -value

# Сдвиг запятой
value /= 10 ** comma_position

# Форматирование результата с округлением до 2 знаков после запятой
value = round(value, 2)

print(f"Значение концентрации: {value:.2f}")

return value

def main():
# Параметры запроса
unit_id = 1
address = 0x00 # Начальный адрес для измеренного значения
count = 2 # Количество регистров для измеренного значения

threshold1_address = 0x02 # Адрес порога 1
threshold2_address = 0x04 # Адрес порога 2

while True:
# Чтение данных измеренного значения
data = read_modbus_data(client, unit_id, address, count)
if data:
print("Текущее значение концентрации:")
interpret_data(data.registers)

# Чтение порога 1
p1_data = read_modbus_data(client, unit_id, threshold1_address, count)
if p1_data:
print("Установленное значение порога 1:")
interpret_data(p1_data.registers, is_threshold=True)

# Чтение порога 2
p2_data = read_modbus_data(client, unit_id, threshold2_address, count)
if p2_data:
print("Установленное значение порога 2:")
interpret_data(p2_data.registers, is_threshold=True)

# Задержка 1 секунда
time.sleep(1)

if __name__ == "__main__":
main()


И получаю

Текущее значение концентрации:
Значение концентрации: 0.09
Установленное значение порога 1:
Значение порога (16-ричное): 0587 0000
Установленное значение порога 2:
Значение порога (16-ричное): 0414 5000

Все верно , но как теперь сделать из значения 0587 0000 число 8.7 , а из значения 0414 5000 число 14.5 . Понятно, что тут это младший байт первого регистра число 8 , но и в нем же 7. (как правильно запятую расставлять?) Во втором пороге младший байт 14 первого регистра и старший второго регистра 50

Евгений Кислов
11.09.2024, 07:21
(как правильно запятую расставлять?)

Это описано на втором скриншоте из вашего первого поста.

У вас есть HEX-строка, содержащее сырое значение: 05 87 00 00

Три младших байта нужно интерпретировать как целое беззнаковое (870000).

Старший байт - это битовое поле.

05 = 0b00000101

Для наглядности визуально выделим отдельные наборы бит: 0b0_00_00_101

0 - знак значения (1 - минус, 0 - плюс)
00 - не используются
00 - состояния сигнализации двух порогов (0 - не сработала, 1 - сработала); каждый бит из двух бит соответствует одному из двух порогов
101 - положение точки в значении (0b101 = 5)

Теперь мы знаем, что реальное значение - это 870000 / 10^5 = 8.7
Если бы бит знака соответствовал минусу - то нужно было бы домножить на -1.0.

В общем, для начала стоит погуглить, как в Python работать с битами - проверить заданный бит целого числа, извлечь порцию бит в отдельную переменную и т. д.

atomo2
11.09.2024, 07:38
Это описано на втором скриншоте из вашего первого поста.

У вас есть HEX-строка, содержащее сырое значение: 05 87 00 00

Три младших байта нужно интерпретировать как целое беззнаковое (870000).

Старший байт - это битовое поле.

05 = 0b00000101

Для наглядности визуально выделим отдельные наборы бит: 0b0_00_00_101

0 - знак значения (1 - минус, 0 - плюс)
00 - не используются
00 - состояния сигнализации двух порогов (0 - не сработала, 1 - сработала); каждый бит из двух бит соответствует одному из двух порогов
101 - положение точки в значении (0b101 = 5)

Теперь мы знаем, что реальное значение - это 870000 / 10^5 = 8.7
Если бы бит знака соответствовал минусу - то нужно было бы домножить на -1.0.

В общем, для начала стоит погуглить, как в Python работать с битами - проверить заданный бит целого числа, извлечь порцию бит в отдельную переменную и т. д.

Спасибо, буду пробовать

Вот если берем HEX-строку 0414 5000

Три младших байта нужно интерпретировать как целое беззнаковое (145000)

Старший байт - это битовое поле.

04 = 0b00000100 положение запятой 4

но у меня получается 0x1450 = 5200 (в десятичной системе) 145000 / 10^4 = 52

А должно быть 14.5 , где я опять туплю?)

1exan
11.09.2024, 08:06
Спасибо, буду пробовать

Вот если берем HEX-строку 0414 5000

Три младших байта нужно интерпретировать как целое беззнаковое (145000)

Старший байт - это битовое поле.

04 = 0b00000100 положение запятой 4

но у меня получается 0x1450 = 5200 (в десятичной системе) 145000 / 10^4 = 52

А должно быть 14.5 , где я опять туплю?)

Не переводите в десятичную - вы же сами пишете: 145000 / 10^4 = 14.5

Валенок
11.09.2024, 08:45
Это не hex, это bcd

melky
11.09.2024, 09:24
у вас bcd это когда тетрада байта не может быть больше 9? то есть в байте никогда не бывает букв от А до F ?


/// <summary>
/// Конвертирование десятичного числа в формат BCD (Используется в MBus, приборах Пульсар)
/// </summary>
/// <param name="dec"></param>
/// <returns></returns>
public static int DecToBCD(int dec)
{
int num = 0;
int num1 = 0;
while (dec != 0)
{
num = num | dec % 10 << (num1 & 31);
num1 += 4;
dec /= 10;
}
return num;
}

/// <summary>
/// Конвертирование числа в формате BCD в десятичный формат (Используется в MBus, приборах Пульсар)
/// </summary>
/// <param name="bcdNumber"></param>
/// <returns></returns>

public static long BcdToDec(byte[] bcdNumber)
{
long result = 0;
foreach (byte b in bcdNumber)
{
int digit1 = b >> 4;
int digit2 = b & 0x0f;
result = (result * 100) + digit1 * 10 + digit2;
}
return result;
}


Ну на ST сами переделывайте. Может и готовое есть. (это C#)

kondor3000
11.09.2024, 09:51
ФБ на ST уже давно выкладывал. Функции аналогично, если надо не побайтно.
Конвертация времени BCD формата панелей, HEX в DEC и обратно DEC в HEX https://owen.ru/forum/showthread.php?t=38239&page=20#200

atomo2
11.09.2024, 12:25
Рабочий код на питоне для считывания текущий концентрации и порогов , может кому пригодится

from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import time

# Параметры подключения
port = 'COM6'
baudrate = 9600
client = ModbusClient(
method='rtu',
port=port,
baudrate=baudrate,
stopbits=1,
parity='N',
timeout=1
)

def read_modbus_data(client, unit_id, address, count):
# Подключение к клиенту
if not client.connect():
print("Не удалось подключиться к Modbus серверу")
return None

# Чтение данных
result = client.read_holding_registers(address, count, unit=unit_id)
if result.isError():
print("Ошибка чтения данных: ", result)
return None

# Закрытие соединения
client.close()
return result

def format_value_with_comma(hex_value, comma_position):
# Преобразование 16-ричного значения в строку
value_str = f"{hex_value:08X}"

# Вставка запятой на нужную позицию
integer_part = value_str[2:-comma_position] if comma_position < len(value_str) else '0'
decimal_part = value_str[-comma_position:] if comma_position > 0 else '0'

# Форматирование с двумя знаками после запятой
formatted_value = f"{integer_part},{decimal_part}".lstrip('0') or '0'

# Обрезаем до двух знаков после запятой
if len(decimal_part) > 2:
formatted_value = f"{integer_part},{decimal_part[:2]}"
return formatted_value

def display_registers(registers):
if len(registers) < 2:
print("Недостаточно данных для отображения")
return

# Извлечение значений регистров
reg0 = registers[0] # Старший регистр
reg1 = registers[1] # Младший регистр

# Объединение регистров в одно 16-ричное число
combined_hex = (reg0 << 16) | reg1

# Позиция запятой из старшего байта первого регистра (используем 8 младших бит)
comma_position = (reg0 >> 8) & 0xFF

# Форматирование значения с запятой
formatted_value = format_value_with_comma(combined_hex, comma_position)

# Вывод только отформатированного значения
print(f"Значение: {formatted_value}")

def display_concentration(registers):
if len(registers) < 2:
print("Недостаточно данных для отображения концентрации")
return

# Извлечение значений регистров
reg0 = registers[0] # Старший регистр
reg1 = registers[1] # Младший регистр

# Разбиение регистров на байты
byte1 = (reg0 >> 8) & 0xFF
byte2 = reg0 & 0xFF
byte3 = (reg1 >> 8) & 0xFF
byte4 = reg1 & 0xFF

# Извлечение состояния сигнализации и знака
sign_bit = (byte1 >> 7) & 0x01
comma_position = (byte1 & 0x07)

# Определение знака
sign = '-' if sign_bit else '+'

# Чтение значений
integer_part = byte2 * 10000 + byte3 * 10 + (byte4 >> 4)
decimal_part = (byte4 & 0x0F) * 100

# Формирование значения
value = integer_part + decimal_part / 10000
if sign_bit:
value = -value

# Сдвиг запятой
value /= 10 ** comma_position

# Форматирование результата с округлением до 2 знаков после запятой
value = round(value, 2)

print(f"Значение концентрации: {value:.2f}")

def main():
# Параметры запроса
unit_id = 1
count = 2 # Количество регистров для порога и концентрации

threshold1_address = 0x02 # Адрес порога 1
threshold2_address = 0x04 # Адрес порога 2
concentration_address = 0x00 # Адрес регистров концентрации

while True:
# Чтение порога 1
p1_data = read_modbus_data(client, unit_id, threshold1_address, count)
if p1_data:
print("Установленное значение порога 1:")
display_registers(p1_data.registers)

# Чтение порога 2
p2_data = read_modbus_data(client, unit_id, threshold2_address, count)
if p2_data:
print("Установленное значение порога 2:")
display_registers(p2_data.registers)

# Чтение концентрации
conc_data = read_modbus_data(client, unit_id, concentration_address, count)
if conc_data:
print("Измеренное значение концентрации:")
display_concentration(conc_data.registers)

# Задержка 1 секунда
time.sleep(1)

if __name__ == "__main__":
main()


Пошел теперь бороться с записью порогов и пгэсов


Вот код для этого, но до 15 включительно записывается правильное значение , а если я ввожу 20 , то записывается 14, если 26 то 20. Ткните носом где у меня преобразование не правильно происходит?

[code python]import tkinter as tk
import serial

# Функция для вычисления CRC16
def calculate_crc(data):
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc.to_bytes(2, byteorder='little')

# Функция для записи значения в Modbus
def write_modbus(set1, value):
# Преобразование значения в формат BCD
bh = bl = bih = 0x00
bil = value & 0xFF # Значение в `BIL` (младший байт)

# Формирование команды
command = bytearray([
0x01, # Адрес устройства
0x10, # Код функции (Запись в несколько регистров)
0x00, 0x20, # Начальный адрес (0x0020)
0x00, 0x03, # Количество регистров (3)
0x06, # Количество байтов данных (6)
set1, # Уставка (1 байт)
0x00, # Нулевой байт
bh, bl, bih, bil # BH, BL, BIH, BIL
])

# Добавление CRC
crc = calculate_crc(command)
command.extend(crc)

# Отладочная информация
print("Команда для отправки:", command.hex().upper())

# Отправка команды по COM-порту
with serial.Serial('COM6', 9600, timeout=1) as ser:
ser.write(command)
response = ser.read(8) # Считывание ответа (если он есть)
print("Ответ:", response.hex().upper())

# Обработчик для кнопки установки порога 1
def on_set_threshold1():
try:
# Чтение значения из текстового поля и преобразование в целое число
value = int(entry_threshold1.get(), 10)
if value < 0 or value > 255:
raise ValueError("Значение должно быть от 0 до 255")

# Отправка команды
write_modbus(0x10, value) # Уставка 0x10 и значение в `BIL`
except ValueError as e:
print("Ошибка:", e)

# Создание интерфейса
root = tk.Tk()
root.title("Modbus Interface")

# Окно для установки первого порога
tk.Label(root, text="Установка порога 1").pack(pady=5)
entry_threshold1 = tk.Entry(root)
entry_threshold1.pack(pady=5)
entry_threshold1.insert(0, '0')
tk.Button(root, text="Установить порог 1", command=on_set_threshold1).pack(pady=5)

root.mainloop()

Мои мысли такие что от 16 мы должны писать уже не только в BIL а и в BH, BL, BIH

Валенок
11.09.2024, 17:41
Мои мысли такие что от 16 мы должны писать уже не только в BIL а и в BH, BL, BIH
почитай про bcd-формат
и см. п#11

atomo2
12.09.2024, 05:03
почитай про bcd-формат
и см. п#11

Спасибо разобрался)

def split_value_bcd(value):

bcd_value = int(f"{value:02d}", 16) # Преобразуем в BCD
# Раскладываем по байтам
bh = 0
bl = 0
bih = (bcd_value >> 8) & 0xFF
bil = bcd_value & 0xFF

Валенок
12.09.2024, 08:03
Что разобрались - хорошо. Непонятно зачем на байты раскладывать. Вы п.11 недочитали