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