--- 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.