Incomplete data being passed to Ignition via OPC device

I am using a raspberry pi 5 powered camera to do some object recognition and sorting. I am passing a fairly small str array to Ignition. What's going wrong is that the data is getting cut off and I'm only getting the back 1/3 of the data, and it's only updating on the first run of the program. I'm aware this could be a coding issue but I'm covering my bases here. Any have any experience with bringing in data from a RPi using the socket() function and have any issues with Ignition handling the data?

Can you go into more detail about your setup here?

Are you trying to tell us you configured a device using Ignition's TCP driver and have it connecting to a program you're running on the RPi?

For the most part yes. I have a RPi5 that I am running an object detection program. I want to send data that is just a string array to Ignition and it will be store in a DB and I will set up a page where employees can query the DB. So currently it does connect, and it does transmit data, but I'm only getting an incomplete set of data and it only works on the first iteration of the program's loop. I'm sure the problem is a mixture of my code and my Ignition setup, I'm just unfamiliar with both. I'll post a code snippet that shows where the data transfer is taking place and a screen shot of my perspective setup as well.

def send_data(client_socket, data_queue):
    while not data_queue.empty():
        data = data_queue.get()
        data_length = len(data)
        length_str = str(data_length).zfill(8)
        length_bytes = length_str.encode()
        message = length_bytes + data.encode()
        client_socket.sendall(message)

# Set up the socket connection
client_socket = socket_connection.initialize_socket()

data_queue = queue.Queue()

# Main Loop of the program, the program waits until a connection with Ignition is set before continuing
# Then it finds out how many AOIs there are to cycle through and takes a photo with the focus based
# on each AOI and processes it. Then outputs the tag data to Ignition, it does this for each AOI to
# ensure all tags are captured. 
while True:

    try:
        # Read the AOI from the rectangles files
        AOI = read_AOI(rectangles)
        print("Areas of Interest: ",AOI)
        
        # Get the number of AOIs(lines) in the rectangles file
        num_AOI = count_AOI(rectangles)
        print("Number of AOIs: ", num_AOI)

        # Loop over the AOIs
        for _ in range(num_AOI):
            # Loop over each set of coordinates (AOI)
            for coords in AOI:
                # Capture image and save to file pathin RAM
                image = set_focus_and_capture(picam2, coords, capture_config, file_path)
                print(f"Processing Image....")
                # Process image with Points and AprilTag script
                processed_image = checkPoints.checkPoints(file_path)
                # Send data across the socket connection
                print('Sending data...')
                send_data(client_socket, data_queue)

                print('Cleaning up...')
                collected = gc.collect()

Packet-based doesn't make sense for TCP and will result in inconsistent behavior. You need to invent a simple framing format so you can configure the TCP driver with a frame delimiter.

1 Like

I don't mean to be obtuse, but I don't know what that means. The length of the message or data packet could vary depending on how many objects it detects. So, I can't make it a fixed length. What do you mean by framing format and how do you suggest I setup a simple framing format? Thanks.

For example, you could decide every message ends with carriage return + newline (\r\n), and then configure the driver to use a frame-based message delimiter of \r\n.

The TCP driver is primarily meant to receive messages from things like barcode scanners that emit simple ASCII-based messages with predictable framing characters. If you need to do something more complex then it might be better to just script it directly in Ignition.

Right, but most networks have a max transmission unit (packet size limit) of ~1500 bytes. After overhead, that'll be ~1400 bytes of payload. Once your message gets bigger than that, it gets sent as multiple packets, breaking the TCP driver's packet-based message mode.

It's not even that predictable... it's just whatever data happens to be available on the socket at the time it's read. Behavior would vary by OS based on how the TCP stack buffers/delivers the data.

Many devices set low-latency mode on sockets, so a buffer flush on the sender will send a packet right then. So, devices can have reliable behavior if messages are always small and written with one buffer flush. Like barcode scanners.

The TCP driver would be a lot less useful if it didn't handle this.

Sure, it does handle it. I'm just trying to clarify the read logic doesn't actually know anything about "packets" in TCP mode.

This isn't how it's actually implemented because it's using Netty, but effectively imagine this psueo code is how "packet based" works in the TCP driver:

Socket socket = ...;
InputStream is = socket.getInputStream();

byte[] buf = new byte[1024];
while (true) {
  int read = is.read(buf);
  if (read > 0) {
    byte[] data = new byte[read];
    System.arrayCopy(buf, 0, data, 0, read);
    processPacket(data);
  }
}

a "packet" is literally whatever we read from the TCP stream. Nothing you should rely on.

Heh. Now you've got me. I must admit that I don't ever use that driver, because I can't stand ambiguity. Running your own sockets with jython can be unambiguously reliable, but is rather non-trivial in Ignition.

Ok, so if I'm understanding what y'all are saying; I have the device setup to received "packets" and a packet usually has a definition like a certain length, beginning/ending character, etc. And since I'm just sending data to it without a "definition" it's still listening or receiving without ever know when it's done. There is also a limit to how much data can be sent and I may be exceding that resulting in the incomplete data I'm getting. My current setup is also unreliable on what data is gets and how it receives it.

To fix it I should include a delimiter or some type of special sequence or character to denote "end of transmission" so that the data transfer can complete and be ready for the next transfer(it's to be noted that the "packet" I'm trying to send occurs about every 10-20 seconds depending on size of data required for processing).

I miss anything or misunderstand anything?

1 Like

No, that's about right. Slap on a delimiter and change the driver configuration so the message delmiter type is "character based", with whatever the delimiter you choose is. See if that gets you anywhere.

If you're trying to pass non-ASCII data you may find that your delimiter ends up being used as part of the message...

Ok, so I changed my code:

def send_data(client_socket, data_queue):
    while not data_queue.empty():
        data = data_queue.get()
        data += "\r\n"  # Add carriage return and newline to the data
        data_length = len(data)
        length_str = str(data_length).zfill(8)
        length_bytes = length_str.encode()
        message = length_bytes + data.encode()
        print(message)
        client_socket.sendall(message)

And I changed the OPC setup:

And now I don't get any data at all. It's showing it's connected, just not seeing anything.

The message delimiter should be \r\n, you used forward slashes.

3 Likes

Ok, after trying anything and everything I could think of it was getting really convoluted and still wasn't working. So, I wrote a separate stand-alone script just to figure out this data transmission thing. I changed some settings in Ignition as well, and was able to get good data....but only 4 times.

I have no idea why. I've made a few tweaks and changes, but every time it only updates 4 times and then stops. I am at a loss why 4 is the magic number.

import socket
import threading
import time

def socket_connection():
    host = 'XXX.XXX.XXX.XXX'
    port = 8000
    
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind((host, port))
        s.listen()
        print('server listening')
        
        conn, addr = s.accept()
        print('connected')
        
        arr = []
        counter = 1
        
        while True:
            
            arr.append(counter)
            counter += 1
            arr_str = str(arr)
            message = arr_str + "<END>"
            print(message)
            time.sleep(3)
            conn.sendall(arr_str.encode())
            print('data sent')
            time.sleep(3)
                
       
            
thread = threading.Thread(target=socket_connection)
thread.start()  

This is from my console on the RPi, so it is amending the array. It just stops updating in Ignition.

image

Set the "Message Delimiter Type" setting to CharacterBased?

If I change it to CharacterBased I get no data.

Edit: I started playing with the time.sleep() variables in my code just to see if it was a data length, time, or iteration based problem. I noticed when I reduced the delay I got bunched up data, like it wasn't separating the arrays or noticing the end of a message.

This was with 0.2s delay.

This was with all the delays removed

You aren't sending the message with the '<END>' on it.

Thank you, I fixed that and made it CharacterBased but it still stopped at 4 iterations.