Modbus 레지스터 작업 지시 핸드셰이크
명령 코드에 인접하여 순번 레지스터를 두면 잔류한 값과 새 명령을 구분할 수 있다.
문제 정의
Modbus가 제공하는 동작은 레지스터 읽기와 쓰기뿐이며, 장비가 먼저 통신을 개시할 수도 없다. 작업을 지시하고 완료 여부를 확인하려면 어느 레지스터를 누가 언제 쓰는지 규약으로 정해야 한다. 이 규약이 핸드셰이크이다.
레지스터 배치
명령을 내리는 측이 상위이다.
| 주소 | 이름 | 쓰는 측 | 값 |
|---|---|---|---|
| 100 | 명령 코드 | 상위 | 0 없음, 1 이상은 작업 종류 |
| 101 | 명령 순번 | 상위 | 명령마다 1씩 증가 |
| 110 | 상태 | 장비 | 0 대기, 1 실행 중, 2 완료, 3 실패 |
| 111 | 처리한 순번 | 장비 | 수락한 명령의 순번 |
처리 순서
1. 상위 상태 0을 확인하고 명령 코드와 순번을 쓴다
2. 장비 상태를 1로 바꾸고 처리한 순번을 적는다
3. 장비 끝나면 상태를 2나 3으로 바꾼다
4. 상위 결과를 읽고 명령 코드를 0으로 지운다
5. 장비 명령 코드가 0이면 상태를 0으로 되돌린다
잔류 값 문제
명령 코드만 있으면 같은 작업을 연속으로 지시할 때 값이 변경되지 않아 새 명령인지 판별할 수 없다. 상위가 재시작하면 남아 있는 완료 표시가 어느 명령에 대한 것인지도 판별할 수 없다.
장비는 명령 순번이 처리한 순번과 다를 때만 작업을 시작하고, 상위는 처리한 순번이 자신이 쓴 명령 순번과 같을 때만 결과로 인정한다. 같은 순번으로 다시 써도 작업은 한 번만 실행된다.
import time
def run_command(client, code, seq, timeout=30):
# 상태가 0(대기)인지는 부르는 쪽이 먼저 확인한다. 명령 코드와 순번을 한 요청으로 쓴다
client.write_registers(100, [code, seq])
end = time.monotonic() + timeout
while time.monotonic() < end:
rr = client.read_holding_registers(110, count=2)
if not rr.isError():
state, done_seq = rr.registers
# 내 순번의 결과만 본다
if done_seq == seq and state in (2, 3):
client.write_register(100, 0)
return state == 2
time.sleep(0.2)
return False