안녕하세요.
1편에 이어 ModbusTCP에 대해서 확인해 보겠습니다.
테스트 환경은 라즈베리파이 CM4에서 진행했습니다. 이번 글의 목표는 간단하게 pyModbusTCP를 설치해 보고 작동하는 되는 것을 확인하는 것입니다.
1. pymodbusTCP 설치
# install the last available release (stable)
$ pip install pyModbusTCP
설치하려고 하면 아래와 같이 에러가 발생할 수 있습니다. 그래서 가상의 환경을 만들어 진행합니다.
가상 환경을 만들더라도 네트워크 환경은 동일하게 유지되며, 가상 환경은 Python 패키지와 종속성만을 격리할 뿐, 네트워크 설정이나 시스템 자원에는 영향을 주지 않습니다.
1) 가상 환경 생성
$ python3 -m venv ~/virtualEnv
2) 가상 환경 활성화
$ source ~/virtualEnv/bin/activate
3) 패키지 설치
가상 환경 안에서 pip 명령어를 사용하여 라이브러리를 설치합니다.
$ pip install pymodbusTCP
$ pip install RPi.GPIO
4) 가상 환경 종료
작업이 끝난 후 가상 환경을 종료합니다.
(virtualEnv) $ deactivate
2. DataBank Class 간략한 설명
아래의 코드를 작성하기 전에 몇 가지를 알고 있습니다.
DataBank는 pyModbusTCP에서 ModbusTCP 서버의 메모리를 관리하는 클래스입니다.
Modbus 프로토콜에서 데이터를 저장하고 클라이언트 요청 시 응답하는 역할을 담당합니다.
1) 주요 메서드 (이전버전)
(1) set_words(address, values):
특정 주소(address)에 16비트 정수 값(Word)을 저장합니다.
예: DataBank.set_words(0, [100]) → 주소 0에 값 100 저장.
(2) get_words(address, count)
특정 주소부터 지정한 개수(count)만큼의 데이터를 읽습니다.
예: DataBank.get_words(0, 1) → 주소 0에서 1개의 값 읽기.
(3) set_bits(address, values)
특정 주소에 1비트 값(Bit)을 저장.
예: DataBank.set_bits(0, [True, False]) → 주소 0과 1에 각각 True, False 저장.
(4) get_bits(address, count):
특정 주소부터 지정한 개수만큼의 1비트 값을 읽음.
2) Modbus 메모리 구조
pyModbusTCP의 DataBank는 Modbus 표준 메모리 영역(Coils, Discrete Inputs, Holding Registers, Input Registers)을 관리하는 클래스로, 자동으로 생성되며, 사용자가 필요한 만큼 데이터를 설정하거나 커스터마이징 할 수 있습니다.
(1) 자동 생성되는 기본 구조
DataBank는 서버가 초기화되면 기본적으로 Modbus의 메모리 구조를 제공합니다:
- Coils (1-bit): 읽기/쓰기 가능한 디지털 값.
- Discrete Inputs (1-bit): 읽기 전용 디지털 값.
- Holding Registers (16-bit): 읽기/쓰기 가능한 16비트 값.
- Input Registers (16-bit): 읽기 전용 16비트 값.
(2) 초기 크기
기본적으로 DataBank는 메모리 크기를 제한하지 않고 동적으로 생성됩니다.
주소를 설정하기 전까지는 해당 주소에 값이 없는 상태(None)로 처리됩니다.
서버가 실행되기 전 또는 실행 중에 원하는 주소 영역을 설정하면 자동으로 관리됩니다.
(3) 사용자가 설정 가능
ㄱ) 메모리 초기화
사용자는 특정 메모리 영역을 명시적으로 초기화하거나 데이터를 미리 설정할 수 있습니다.
예제: Holding Registers 초기화
from pyModbusTCP.server import DataBank
# 주소 0번에 값 1234 설정
DataBank.set_words(0, [1234])
# 주소 1번부터 5번까지 0으로 초기화
DataBank.set_words(1, [0, 0, 0, 0, 0])
# 설정된 값을 확인
print(DataBank.get_words(0, 6)) # 출력: [1234, 0, 0, 0, 0, 0]
ㄴ) 데이터 업데이트
set_words 또는 set_bits 메서드를 통해 특정 주소를 업데이트할 수 있습니다.
예제: Coils 데이터 설정
# 주소 0~7에 디지털 값 설정
DataBank.set_bits(0, [True, False, True, False, True, True, False, False])
# 데이터 확인
print(DataBank.get_bits(0, 8)) # 출력: [True, False, True, False, True, True, False, False]
ㄷ) 동적 메모리 확장
DataBank는 사용자가 설정하는 주소에 따라 메모리 영역을 자동으로 확장합니다.
예를 들어, 주소 100번에 값을 설정하면 0번부터 99번까지의 주소는 모두 초기화(None)됩니다.
예제
# 주소 100번에 값 설정
DataBank.set_words(100, [50])
# 데이터 확인
print(DataBank.get_words(95, 10)) # 출력: [None, None, None, None, None, None, None, None, None, 50]
(4) 주소 영역 관리
DataBank의 주소 영역은 사용자가 설계에 따라 명시적으로 관리할 수 있습니다.
예를 들어, 데이터 충돌 방지를 위해 메모리 맵을 설계 하거나, 특정 주소 영역에 센서 데이터를 배정할 수 있습니다.
예제: 메모리 맵 설계
# 메모리 설계
SENSOR_FLOW_RATE_ADDR = 0 # 유량 센서 데이터 저장 주소
TEMPERATURE_SENSOR_ADDR = 10 # 온도 센서 데이터 저장 주소
# 데이터 설정
DataBank.set_words(SENSOR_FLOW_RATE_ADDR, [25]) # 유량 센서 데이터
DataBank.set_words(TEMPERATURE_SENSOR_ADDR, [70]) # 온도 센서 데이터
# 데이터 읽기
print("Flow Rate:", DataBank.get_words(SENSOR_FLOW_RATE_ADDR, 1)) # 출력: [25]
print("Temperature:", DataBank.get_words(TEMPERATURE_SENSOR_ADDR, 1)) # 출력: [70]
(5) 사용자가 설정할 수 있는 추가 옵션
현재 pyModbusTCP에서 제공하는 기능으로는
ㄱ) 주소별 데이터 타입 설정은 불가능: 모든 값은 내부적으로 정수형으로 저장됩니다.
ㄴ) 초기 메모리 크기 설정은 필요 없음: 메모리는 동적으로 생성되기 때문에 초기화 과정을 생략해도 됩니다.
3. 소스코드 작성
간단하게 서버를 실행해서 번 메모리 주소에 랜덤데이터를 저장하고 확인하는 소스코드입니다.
1) 전체 소스코드
#!/bin/python
from pyModbusTCP.server import ModbusServer, DataBank
from time import sleep
from random import uniform
# Create an instance of ModbusServer
server = ModbusServer("192.168.0.59", 1502, no_block=True)
try:
print("Start server...")
server.start()
print("Server is online")
state = [0]
while True:
DataBank.set_words(1, [int(uniform(0, 100))])
if state != DataBank.get_words(2):
state = DataBank.get_words(2)
print("Value of Register 2 has changed to " +str(state))
sleep(0.5)
except:
print("Shutdown server ...")
server.stop()
print("Server is offline")
2) 주요 부분별 설명
(1) 라이브러리 임포트
from pyModbusTCP.server import ModbusServer, DataBank
from time import sleep
from random import uniform
pyModbusTCP.server: ModbusTCP 서버를 구현하기 위한 라이브러리
time.sleep: 주기적인 작업을 위해 사용
random.uniform: 랜덤 한 값을 생성하기 위해 사용
(2) ModbusServer 인스턴스 생성
server = ModbusServer("192.168.0.59", 502, no_block=True)
192.168.0.59: 서버의 IP 주소
502: ModbusTCP의 기본 포트 번호
no_block=True: 비차단 모드로 서버를 실행
(3) 서버 시작
try:
print("Start server...")
server.start()
print("Server is online")
서버를 시작하고 온라인 상태를 출력
(4) 데이터 처리 루프
state = [0]
while True:
DataBank.set_words(0, [int(uniform(0, 100))])
if state != DataBank.get_words(1):
state = DataBank.get_words(1)
print("Value of Register 1 has changed to " +str(state))
sleep(0.5)
DataBank.set_words(1, [int(uniform(0, 100))]): 주소 1에 랜덤 한 값을 설정
DataBank.get_words(1): 주소 1의 값을 읽어와서 상태가 변경되었는지 확인
상태가 변경되면 새로운 값을 출력
(5) 서버 종료
except:
print("Shutdown server ...")
server.stop()
print("Server is offline")
예외가 발생하면 서버를 종료하고 오프라인 상태를 출력.
4. 실행 결과
정상적으로 실행 없이 종료되는 경우와
아래와 같이 에러가 발생하는 경우 있을 수 있습니다.
PermissionError: [Errno 13] Permission denied로 인해 발생합니다. 이는 주로 포트 502와 같은 낮은 번호의 포트를 사용하려고 할 때 발생하는 권한 문제입니다. 일반 사용자로는 1024 이하의 포트를 사용할 수 없기 때문에 발생합니다. 몇 가지 방법이 있긴 한데, 간단하게 위의 소스 코드에서 502 포트번호를 1502로 바꿔주면 정상적으로 실행됩니다.
실행은 되지만 참고한 예제가 오랜 전 내용이라 일부 클래스 메서드가 deprecated(더 이상 권장되지 않음)되어 인스턴스 메서드를 사용하는 방식으로 업데이트되었다고 나옵니다. 이 부분은 다음 글에서 다시 확인해 보겠습니다.
소스들도 버전 업하고 한 번에 뚝딱 진행되는 것이 없네요.
기계만 기름칠하고 닦고 하는 것이 아니라, 소스 코드도 지속적으로 손봐주고 관리를 해야 한다고 어떤 분이 한 말이 문뜩 생각이 드네요.
감사합니다.
<참고 사이트>
1. Welcome to pyModbusTCP’s documentation
https://pymodbustcp.readthedocs.io/en/latest/index.html
2. pyModbusTCP - the easy way to a Modbus TCP server with Python
https://www.youtube.com/watch?v=FYPQgnQE9fk
'Embedded > RaspberryPI' 카테고리의 다른 글
Raspberry Pi 가상환경(-m venv)에서 add_event_detect 에러 발생 시 차선책(?) (0) | 2024.11.27 |
---|---|
Raspberry Pi CM4에 ModbusTCP Server 실행해 보기 - 3편(ModbusTCP server 수정된 함수사용) (0) | 2024.11.26 |
Raspberry Pi CM4에 ModbusTCP Server 설치해 보기 - 1편(ModbusTCP 기본지식) (0) | 2024.11.19 |
Raspberry Pi에서 유량센서(YF-B10-S)로 유량계산하기 (2) | 2024.11.14 |
Raspberry pi CM4에 OpenCV 4.6.0과 Contrib 모듈 설치해보기 (2) | 2024.11.12 |