144 lines
5.1 KiB
Markdown
Executable File
144 lines
5.1 KiB
Markdown
Executable File
---
|
||
creation date: 2024-08-22
|
||
modification date: Thursday 22nd August 2024 09:03:26
|
||
tags:
|
||
---
|
||
# Preamble
|
||
### Notable Links
|
||
[[Setting up a virtual python environment (venv)]]
|
||
## What are we doing?
|
||
Doing the beginning pymodbus tutorial on the bone
|
||
## Why are we doing it?
|
||
To get it communicating with ARCADE
|
||
## Who cares?
|
||
Me DGC, RL
|
||
|
||
# First, pymodbus tutorial.
|
||
I'm going to try to do the pymodbus tutorial just communicating over ethernet. Try to set up a client and a server, and then I'll put one of those on the beaglebone.
|
||
|
||
A brief detour: [[Setting up Neovim for Python]]
|
||
|
||
Got this done locally! Here are some notes from ChatGPT working through with it.
|
||
### Client-Server Communication with pymodbus:
|
||
|
||
- **Objective**: Set up a Modbus client-server communication where the client sends a sine wave value, and the server multiplies it by 100 before sending it back.
|
||
|
||
### Server:
|
||
|
||
- **Registers Setup**: The server uses a `ModbusSequentialDataBlock` to hold the values written by the client.
|
||
- **Context**: The `context` in pymodbus acts as a structure to hold different blocks of Modbus memory, such as holding registers, input registers, etc.
|
||
- **Port Binding**: Originally used port `502` (privileged port), but switched to port `5020` to avoid permission issues when running without admin/root privileges.
|
||
- **Issue Fix**: Run with `sudo` for privileged ports or switch to non-privileged ports (above 1024) like `5020` for development.
|
||
|
||
### Client:
|
||
|
||
- **Sine Wave Generation**: The client calculates a sine wave based on the current timestamp and writes it to the Modbus server.
|
||
- **Scaling Issue**: The sine wave was initially scaled by 10,000, resulting in out-of-range values for 16-bit registers (valid range: 0–65535).
|
||
- **Solution 1 (Clipping)**: Clamp the sine wave to ensure values remain within `0–65535` to avoid struct errors.
|
||
- **Solution 2 (Shift & Scale)**: Shift the sine wave by adding `1` to make all values positive, and then scale the sine wave to fit the `0–65535` range.
|
||
|
||
### Key Notes:
|
||
|
||
- **Addressing Error**: The `struct.error` occurred because the sine wave value multiplied by `10000` was out of the valid range for a 16-bit unsigned integer.
|
||
- **Fix**: Use either clipping or a shift-and-scale approach to keep the sine wave within the Modbus register's valid range.
|
||
- **Read Registers**: The Modbus `read_holding_registers()` function requires integer arguments for the address and count, with an optional slave ID.
|
||
- **Structure**: Always check that values passed to Modbus functions (like `write_register`) are within the range expected by the protocol (e.g., 0 to 65535 for 16-bit registers).
|
||
|
||
Let me know if you'd like to expand on any points!
|
||
|
||
Here are the scripts
|
||
### Synchronous TCP Client
|
||
```python
|
||
import asyncio
|
||
import numpy as np
|
||
import time
|
||
import pymodbus.client as ModbusClient
|
||
from pymodbus import (
|
||
ExceptionResponse,
|
||
FramerType,
|
||
ModbusException,
|
||
pymodbus_apply_logging_config
|
||
)
|
||
|
||
# GLOBAL VARIABLES
|
||
client = "127.0.0.1" #wherever the client lives
|
||
server = "127.0.0.1" # wherever the beaglebone lives
|
||
|
||
#pymodbus_apply_logging_config("DEBUG")
|
||
print("Setting up...")
|
||
client = ModbusClient.ModbusTcpClient(
|
||
server,
|
||
port = 5020
|
||
)
|
||
|
||
print("Connecting to server...")
|
||
client.connect()
|
||
assert client.connected
|
||
|
||
|
||
i=0
|
||
|
||
while True:
|
||
print(" ")
|
||
print("______________________")
|
||
print("NEW LOOP!")
|
||
|
||
# Create some sine wave value
|
||
sine_value = np.sin(i)+1
|
||
print("Current sin() value is: ", sine_value)
|
||
i+=0.1
|
||
|
||
# Write the value to the server
|
||
print("Writing to slave...")
|
||
client.write_register(0, int(sine_value * 10000), 1)
|
||
print("done!")
|
||
|
||
#time.sleep(1)
|
||
|
||
# Read value back from the server
|
||
print("Reading register from server...")
|
||
response = client.read_holding_registers(0, 1)
|
||
if response.isError():
|
||
print("Error reading from the server!")
|
||
else:
|
||
modified_value = response.registers[0]
|
||
print("Response from the server: ", modified_value/10000)
|
||
|
||
time.sleep(1)
|
||
```
|
||
|
||
### Synchronous Server Client
|
||
```python
|
||
from pymodbus.server import StartTcpServer
|
||
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
|
||
from pymodbus.datastore import ModbusSequentialDataBlock
|
||
|
||
import logging
|
||
logging.basicConfig()
|
||
log = logging.getLogger()
|
||
log.setLevel(logging.DEBUG)
|
||
|
||
# GLOBAL VARIABLES
|
||
client_ip = "127.0.0.1"
|
||
server_ip = "127.0.0.1"
|
||
port = 5020
|
||
|
||
# Setup the datastore
|
||
store = ModbusSlaveContext(
|
||
di=ModbusSequentialDataBlock(0, [0]*100), #direct inputs (sensor statuses...)
|
||
co=ModbusSequentialDataBlock(0, [0]*100), #coils (think of relay positions, interal logic)
|
||
hr=ModbusSequentialDataBlock(0, [0]*100), #holding registers (internal memory)
|
||
ir=ModbusSequentialDataBlock(0, [0]*100)) #input registers (sensor values...)
|
||
context = ModbusServerContext(slaves=store, single=True)
|
||
|
||
# Start the server
|
||
def run_server():
|
||
StartTcpServer(context=context, address=(client_ip,port))
|
||
|
||
if __name__ == "__main__":
|
||
run_server()
|
||
```
|
||
|
||
## Putting it on the BeagleBone
|
||
I'm putting the server on the bone
|
||
Need to get virtual environment set up on the bone.... This is too annoying to conquer today. |