upload previous file

This commit is contained in:
Dongubak
2026-08-07 13:44:56 +09:00
parent 5c17a6e99c
commit 05ea17dfb2
14 changed files with 267 additions and 3 deletions
+73 -3
View File
@@ -1,4 +1,74 @@
# fori_ZLTECHDRIVER_ID_SET
# ZLAC8015D V4 RS485 슬레이브 ID 운용 가이드
fori_ZLTECHDRIVER_ID_SET
ZLTECH 드라이버의 RS485 ID 부여 코드입니다.
ZLTECH ZLAC8015D V4 듀얼 드라이버의 RS485(Modbus RTU) 슬레이브 ID를 확인/변경하기 위한 파이썬 스크립트 모음.
프로토콜 세부 사항은 동봉된 `ZLAC8015D V4 Series RS485 Communication Version 1.06` 매뉴얼 기준.
## 사전 준비
```bash
python3 -m venv .venv
./.venv/bin/pip install -r requirements.txt # pymodbus + pyserial
```
- 통신 라이브러리로 `pymodbus`(ModbusSerialClient, RTU) 사용
- RS485-USB 어댑터로 드라이버와 연결 (A/B 라인, GND 공통)
- 기본 통신 설정: Baud rate 115200, 8 data bit, No parity, Stop bit 1
- 드라이버 출고 시 기본 슬레이브 ID는 **1**
- 실제 하드웨어 운용 테스트는 우분투 환경에서 진행 예정 (개발/문법 검증은 macOS에서 완료)
## 파일 구성
| 파일 | 역할 |
|---|---|
| `zltech_modbus.py` | pymodbus 기반 통신 공용 함수 (read/write register) |
| `zltech_scan_ids.py` | 버스에 연결된 슬레이브 ID 스캔 |
| `zltech_set_id.py` | 슬레이브 ID 변경 (EEPROM 저장 포함) |
| `requirements.txt` | 의존 패키지 (pymodbus, pyserial) |
## 1. 슬레이브 ID 확인 (스캔)
```bash
./.venv/bin/python zltech_scan_ids.py --port /dev/tty.usbserial-XXXX --range 1 16
```
- `--port`: 시리얼 포트 (Ubuntu: `/dev/ttyUSB0` 등, macOS: `/dev/tty.usbserial-XXXX`, Windows: `COM5` 등)
- `--baud`: 기본값 115200
- `--range START END`: 스캔할 ID 범위 (기본 1~16)
응답한 ID 목록이 출력됨.
## 2. 슬레이브 ID 변경 (1과 2로 설정)
⚠️ **두 드라이버 모두 기본 ID가 1**이라, 동시에 버스에 연결하면 응답이 충돌합니다.
**반드시 한 번에 하나씩만 연결**한 상태로 진행하세요.
### 순서
1. 드라이버 A만 연결 → 기본값 그대로 ID 1 유지 (변경 불필요)
2. 드라이버 A 전원 off, 분리
3. 드라이버 B만 연결 → ID를 2로 변경
```bash
./.venv/bin/python zltech_set_id.py --port /dev/tty.usbserial-XXXX --old-id 1 --new-id 2
```
4. 드라이버 B 전원을 껐다 켜서(파워사이클) 변경 사항 완전 반영
5. 이제 드라이버 A(ID 1), B(ID 2)를 같은 버스에 함께 연결
6. 확인:
```bash
./.venv/bin/python zltech_scan_ids.py --port /dev/tty.usbserial-XXXX --range 1 2
```
ID 1, 2 모두 응답하면 성공.
## 참고: 레지스터 주소
| 주소 | 이름 | 범위 | 비고 |
|---|---|---|---|
| `0x2001` | RS485 Custom Drive Node Number | 1~127 | 기본값 1 |
| `0x2002` | RS485 custom communication baud rate | 1~6 (128000~9600bps) | 기본값 2 (115200bps) |
| `0x2010` | RW 속성 파라미터 EEPROM 저장 | 0/1 | 1 = 저장 |
자세한 레지스터 목록은 동봉 매뉴얼의 "Address Directionary" 섹션 참고.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
pymodbus>=3.7
pyserial>=3.5
Binary file not shown.
+55
View File
@@ -0,0 +1,55 @@
"""pymodbus-based helpers for the ZLAC8015D V4 RS485 (Modbus RTU) protocol.
Register reference (from "ZLAC8015D V4 Series RS485 Communication" manual):
0x2001 RS485 Custom Drive Node Number (RW/S, range 1-127, default 1)
0x2002 RS485 custom communication baud rate (RW/S)
0x2010 Save all RW attribute parameters to EEPROM (write 1 to save)
Requires: pip install pymodbus pyserial
"""
from pymodbus.client import ModbusSerialClient
REG_NODE_ID = 0x2001
REG_SAVE_EEPROM = 0x2010
# pymodbus renamed the "slave id" keyword across major versions
# (unit -> slave -> device_id). Try each so this works regardless of
# which pymodbus version is installed (e.g. on the Ubuntu test box).
_ID_KWARGS = ("device_id", "slave", "unit")
def _call(fn, *args, node_id, **kwargs):
last_err = None
for kw in _ID_KWARGS:
try:
return fn(*args, **{kw: node_id}, **kwargs)
except TypeError as exc:
last_err = exc
continue
raise TypeError(f"pymodbus client does not accept any of {_ID_KWARGS}: {last_err}")
def make_client(port: str, baud: int = 115200, timeout: float = 0.3) -> ModbusSerialClient:
return ModbusSerialClient(
port,
baudrate=baud,
bytesize=8,
parity="N",
stopbits=1,
timeout=timeout,
)
def read_register(client: ModbusSerialClient, node_id: int, address: int, count: int = 1):
"""Return list of register values, or None on error/no response."""
result = _call(client.read_holding_registers, address, node_id=node_id, count=count)
if result is None or result.isError():
return None
return result.registers
def write_register(client: ModbusSerialClient, node_id: int, address: int, value: int) -> bool:
result = _call(client.write_register, address, value, node_id=node_id)
if result is None or result.isError():
return False
return True
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Scan an RS485 bus for ZLAC8015D drivers and report which node IDs answer.
Requires: pip install pymodbus pyserial
Example:
python3 zltech_scan_ids.py --port /dev/tty.usbserial-XXXX
python3 zltech_scan_ids.py --port COM5 --baud 115200 --range 1 10
"""
import argparse
import sys
from zltech_modbus import REG_NODE_ID, make_client, read_register
def scan(port: str, baud: int, id_range, timeout: float):
found = []
client = make_client(port, baud, timeout)
if not client.connect():
print(f"ERROR: could not open serial port {port}")
sys.exit(1)
try:
for node_id in id_range:
values = read_register(client, node_id, REG_NODE_ID, 1)
if values is not None:
found.append((node_id, values[0]))
print(f" id {node_id:3d}: responded (node-id register reads {values[0]})")
finally:
client.close()
return found
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--port", required=True, help="Serial port, e.g. /dev/tty.usbserial-XXXX or COM5")
parser.add_argument("--baud", type=int, default=115200, help="Baud rate (default 115200, the driver default)")
parser.add_argument("--range", nargs=2, type=int, metavar=("START", "END"), default=[1, 16],
help="Inclusive node-ID range to scan (default 1 16)")
parser.add_argument("--timeout", type=float, default=0.15, help="Per-ID response timeout in seconds")
args = parser.parse_args()
start, end = args.range
id_range = range(start, end + 1)
print(f"Scanning {args.port} @ {args.baud}bps for node IDs {start}-{end} ...")
found = scan(args.port, args.baud, id_range, args.timeout)
print()
if found:
print(f"Found {len(found)} driver(s):")
for node_id, _ in found:
print(f" -> ID {node_id}")
else:
print("No drivers responded. Check wiring, baud rate, and that only powered drivers are on the bus.")
sys.exit(1)
if __name__ == "__main__":
main()
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Set the RS485 node ID of a ZLAC8015D driver.
Only ONE driver should be connected to the RS485 bus while running this
script (both drivers ship with default ID 1, so with two on the bus their
replies would collide). Recommended workflow to end up with IDs 1 and 2:
1. Connect ONLY the first driver. Leave it at the default ID:
python3 zltech_set_id.py --port <PORT> --old-id 1 --new-id 1
(or just skip it, it is already 1)
2. Power off, disconnect it, connect ONLY the second driver.
3. Set its ID to 2:
python3 zltech_set_id.py --port <PORT> --old-id 1 --new-id 2
4. Power-cycle that driver.
5. Now connect both drivers to the same bus; they will answer as
ID 1 and ID 2 respectively.
Requires: pip install pymodbus pyserial
"""
import argparse
import sys
import time
from zltech_modbus import REG_NODE_ID, REG_SAVE_EEPROM, make_client, read_register, write_register
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--port", required=True, help="Serial port, e.g. /dev/tty.usbserial-XXXX or COM5")
parser.add_argument("--baud", type=int, default=115200, help="Baud rate (default 115200, the driver default)")
parser.add_argument("--old-id", type=int, required=True, help="Current node ID of the driver (1-127)")
parser.add_argument("--new-id", type=int, required=True, help="New node ID to assign (1-127)")
parser.add_argument("--timeout", type=float, default=0.3, help="Response timeout in seconds")
args = parser.parse_args()
for name, value in (("--old-id", args.old_id), ("--new-id", args.new_id)):
if not (1 <= value <= 127):
parser.error(f"{name} must be in range 1-127")
client = make_client(args.port, args.baud, args.timeout)
if not client.connect():
print(f"ERROR: could not open serial port {args.port}")
sys.exit(1)
try:
print(f"Checking driver at ID {args.old_id} ...")
values = read_register(client, args.old_id, REG_NODE_ID, 1)
if values is None:
print(f"ERROR: no response from ID {args.old_id}. Check wiring/baud rate, "
f"and that exactly one driver is on the bus.")
sys.exit(1)
print(f" driver responded, current node-id register = {values[0]}")
print(f"Writing new node ID {args.new_id} ...")
if not write_register(client, args.old_id, REG_NODE_ID, args.new_id):
print("ERROR: write did not get a valid response.")
sys.exit(1)
# The ID change can take effect immediately, so try to save under
# the new ID first, and fall back to the old ID if that fails.
time.sleep(0.05)
saved = write_register(client, args.new_id, REG_SAVE_EEPROM, 1)
if not saved:
saved = write_register(client, args.old_id, REG_SAVE_EEPROM, 1)
if saved:
print("Saved to EEPROM.")
else:
print("WARNING: could not confirm the save-to-EEPROM write; "
"the ID change may be lost on power-cycle.")
print(f"Done. Power-cycle the driver, then verify with:\n"
f" python3 zltech_scan_ids.py --port {args.port} --range {args.new_id} {args.new_id}")
finally:
client.close()
if __name__ == "__main__":
main()