Спасибо, мне казалось что и так может тоже;)
Подскажите еще пожалуйста вот такой момент, разбираюсь в чужом проекте Вложение 85317 Вложение 85318
Кнопка включения ПВС на панели имеет адрес 0x44, как я понял ведь должен быть адрес 3x0:05 (ON_OFF_PVS_FUG)
ПВС вообще не работает и я вот не пойму проблема в неправильном адресе кнопки или вообще программный блок не полный Вложение 85319 Вложение 85320 Вложение 85324
Как я понял MAN_Regulator это ручной ввод(оно нам не интересно) , а нас интересует AUTO_MAN атематическое регулирование? а ON_OFF_PVS_FUG это сброс регулятора и разрешение работы.
И мне нужно включить AUTO_MAN и ON_OFF_PVS_FUG
Вложение 85322 проект плк
Вложение 85323 проект панели
Обновил программу для подсчета адресов из конфигурации плк теперь считает как 3x , и так же пересчитывает в 0x
Код:import tkinter as tk
from tkinter import ttk, messagebox, filedialog
class ModbusAddressCalculatorOrder(tk.Tk):
def __init__(self):
super().__init__()
self.title("Калькулятор адресов")
self.geometry("750x700")
self.resizable(True, True)
self.blocks = []
self.create_widgets()
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.rowconfigure(3, weight=1)
self.rowconfigure(6, weight=1)
def create_widgets(self):
padding = {'padx': 10, 'pady': 5}
ttk.Label(self, text="Тип блока:").grid(column=0, row=0, sticky="w", **padding)
self.type_var = tk.StringVar()
self.type_combo = ttk.Combobox(self, textvariable=self.type_var,
values=["Float", "2 byte", "8 bit", "4 bytes"], state="readonly")
self.type_combo.grid(column=1, row=0, sticky="ew", **padding)
self.type_combo.current(0)
ttk.Label(self, text="Количество:").grid(column=0, row=1, sticky="w", **padding)
self.count_entry = ttk.Entry(self)
self.count_entry.grid(column=1, row=1, sticky="ew", **padding)
self.count_entry.insert(0, "1")
self.btn_add = ttk.Button(self, text="Добавить блок", command=self.add_block)
self.btn_add.grid(column=0, row=2, columnspan=2, pady=10, sticky="ew")
# Галочка для пересчета адресов в 0x
self.use_hex_var = tk.BooleanVar(value=False)
self.chk_hex = ttk.Checkbutton(self, text="Показывать адреса в 0x", variable=self.use_hex_var)
self.chk_hex.grid(column=0, row=2, columnspan=2, sticky="e", padx=10)
self.btn_load_exp = ttk.Button(self, text="Загрузить .EXP", command=self.load_exp_file)
self.btn_load_exp.grid(column=0, row=7, columnspan=2, pady=10, sticky="ew")
tree_frame = ttk.Frame(self)
tree_frame.grid(column=0, row=3, columnspan=2, sticky="nsew", padx=10, pady=5)
tree_frame.columnconfigure(0, weight=1)
tree_frame.rowconfigure(0, weight=1)
self.tree = ttk.Treeview(tree_frame, columns=("Тип", "Количество"), show="headings")
self.tree.heading("Тип", text="Тип блока")
self.tree.heading("Количество", text="Количество")
self.tree.column("Тип", width=250)
self.tree.column("Количество", width=100)
self.tree.grid(column=0, row=0, sticky="nsew")
tree_scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
tree_scrollbar.grid(column=1, row=0, sticky="ns")
self.tree.configure(yscrollcommand=tree_scrollbar.set)
btn_frame = ttk.Frame(self)
btn_frame.grid(column=0, row=4, columnspan=2, pady=10, sticky="ew")
btn_frame.columnconfigure((0, 1, 2), weight=1)
ttk.Button(btn_frame, text="Удалить блок", command=self.delete_block).grid(column=0, row=0, padx=5, sticky="ew")
ttk.Button(btn_frame, text="Вверх", command=self.move_up).grid(column=1, row=0, padx=5, sticky="ew")
ttk.Button(btn_frame, text="Вниз", command=self.move_down).grid(column=2, row=0, padx=5, sticky="ew")
self.btn_calc = ttk.Button(self, text="Рассчитать адреса", command=self.calculate_addresses)
self.btn_calc.grid(column=0, row=5, columnspan=2, pady=10, sticky="ew")
self.text_frame = ttk.Frame(self)
self.text_frame.grid(column=0, row=6, columnspan=2, sticky="nsew", padx=10, pady=5)
self.text_frame.columnconfigure(0, weight=1)
self.text_frame.rowconfigure(0, weight=1)
self.result_text = tk.Text(self.text_frame, wrap="none", font=("Consolas", 10))
self.result_text.grid(column=0, row=0, sticky="nsew")
self.scrollbar_v = ttk.Scrollbar(self.text_frame, orient="vertical", command=self.result_text.yview)
self.scrollbar_v.grid(column=1, row=0, sticky="ns")
self.result_text.configure(yscrollcommand=self.scrollbar_v.set)
self.scrollbar_h = ttk.Scrollbar(self.text_frame, orient="horizontal", command=self.result_text.xview)
self.scrollbar_h.grid(column=0, row=1, sticky="ew")
self.result_text.configure(xscrollcommand=self.scrollbar_h.set)
self.result_text.config(state="normal")
def add_block(self):
t = self.type_var.get()
c = self.count_entry.get()
try:
c = int(c)
if c <= 0:
raise ValueError
except ValueError:
messagebox.showerror("Ошибка", "Введите корректное положительное число для количества!")
return
self.blocks.append({"type": t, "count": c})
self.refresh_tree()
def refresh_tree(self):
self.tree.delete(*self.tree.get_children())
for idx, block in enumerate(self.blocks):
self.tree.insert("", "end", iid=idx, values=(block["type"], block["count"]))
def delete_block(self):
selected = self.tree.selection()
if not selected:
messagebox.showinfo("Информация", "Выберите блок для удаления.")
return
idx = int(selected[0])
del self.blocks[idx]
self.refresh_tree()
def move_up(self):
selected = self.tree.selection()
if not selected:
messagebox.showinfo("Информация", "Выберите блок для перемещения.")
return
idx = int(selected[0])
if idx == 0:
return
self.blocks[idx], self.blocks[idx - 1] = self.blocks[idx - 1], self.blocks[idx]
self.refresh_tree()
self.tree.selection_set(idx - 1)
def move_down(self):
selected = self.tree.selection()
if not selected:
messagebox.showinfo("Информация", "Выберите блок для перемещения.")
return
idx = int(selected[0])
if idx == len(self.blocks) - 1:
return
self.blocks[idx], self.blocks[idx + 1] = self.blocks[idx + 1], self.blocks[idx]
self.refresh_tree()
self.tree.selection_set(idx + 1)
def calculate_addresses(self):
addr = 0
lines = []
use_hex = self.use_hex_var.get() # галочка для hex
def fmt_reg(r):
return f"0x{r:02X}" if use_hex else f"3x{r}"
for idx, block in enumerate(self.blocks):
t = block["type"]
c = block["count"]
if t in ["Float", "4 bytes"]:
length = 2
total_length = c * length
elif t == "2 byte":
length = 1
total_length = c * length
elif t == "8 bit":
total_length = (c + 1) // 2
else:
messagebox.showerror("Ошибка", f"Неизвестный тип блока: {t}")
return
start_block = addr
end_block = addr + total_length - 1
lines.append(f"{idx + 1}. {t} ({c} шт.): от регистра {fmt_reg(start_block)} до {fmt_reg(end_block)}")
if t in ["Float", "2 byte", "4 bytes"]:
for i in range(c):
start_el = addr + i * length
end_el = start_el + length - 1
if length == 1:
lines.append(f" Элемент {i + 1}: регистр {fmt_reg(start_el)}")
else:
lines.append(f" Элемент {i + 1}: регистры {fmt_reg(start_el)} - {fmt_reg(end_el)}")
elif t == "8 bit":
for i in range(c):
reg = addr + i // 2
bit_pos = (i % 2) * 8
bit_addresses = [reg * 16 + bit_pos + b for b in range(8)]
bit_addresses_str = ", ".join(fmt_reg(b) for b in bit_addresses)
lines.append(f" Элемент {i + 1}: регистр {fmt_reg(reg)}, биты {bit_pos}-{bit_pos + 7} → битовые адреса [{bit_addresses_str}]")
addr = end_block + 1
if lines:
lines.append(f"\nИтоговый конечный адрес регистра: {fmt_reg(addr - 1)}")
else:
lines.append("Список блоков пуст")
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "\n".join(lines))
def load_exp_file(self):
filepath = filedialog.askopenfilename(
title="Выберите файл .EXP",
filetypes=[("EXP files", "*.exp"), ("Все файлы", "*.*")]
)
if not filepath:
return
blocks_from_file = self.parse_exp_modules(filepath)
if blocks_from_file:
self.blocks = []
for count, block_type in blocks_from_file:
self.blocks.append({"type": block_type, "count": count})
self.refresh_tree()
messagebox.showinfo("Готово", f"Загружено {len(blocks_from_file)} блоков из файла.")
else:
messagebox.showwarning("Внимание", "Не удалось найти подходящие блоки в файле.")
def parse_exp_modules(self, filename):
results = []
count = 0
prev_module = None
module_aliases = {
'float': 'Float',
'2 byte': '2 byte',
'8 bits': '8 bit',
'4 byte': '4 bytes'
}
encodings = ['utf-8', 'cp1251', 'latin1']
for enc in encodings:
try:
with open(filename, 'r', encoding=enc) as f:
for line in f:
line = line.strip()
if line.startswith("_MODULE_NAME:"):
start = line.find("'")
end = line.rfind("'")
if start != -1 and end != -1 and end > start:
raw = line[start + 1:end].strip().lower()
block_type = module_aliases.get(raw)
if not block_type:
if prev_module is not None:
results.append((count, prev_module))
prev_module = None
count = 0
continue
if block_type == prev_module:
count += 1
else:
if prev_module is not None:
results.append((count, prev_module))
prev_module = block_type
count = 1
if prev_module is not None:
results.append((count, prev_module))
break
except Exception:
continue
return results
if __name__ == "__main__":
app = ModbusAddressCalculatorOrder()
app.mainloop()
Вложение 85325 Вложение 85326

