As well as using the groov Manage REST API, you can also access groov EPIC data through Opto’s Memory-Mapped Protocol (OptoMMP). OptoMMP can access both system information and physical I/O points with packages of bytes that follow the IEEE 1394 standard format, which essentially means they follow a strict but consistent structure.
Beyond being able to open a socket to port 2001 on the controller, there aren’t many system or software requirements to use OptoMMP, so even Python scripts can make powerful control commands with the right packages – the hard part is building the package. Once the package is built, it is sent through the socket, a response is received, unpacked, and printed to the console to give read result or write success/failure.
To figure out which package to use and what it looks like, refer to the OptoMMP Protocol Manual. This document goes deep into the details of the protocol and how to use it. The Overview of Custom Application Programming section should be especially handy, that’s where you’ll find the binary breakdown of every package type.
Using the manual and taking some inspiration from the C++ SDK for OptoMMP I built three Python example scripts that can check a controller up-time, toggle a digital output, and read a digital output, all using OptoMMP.
Basic instructions are commented at the top of each script, but for more details on how these scripts were written or how the memory map packages are built, check out this developer tutorial.
Happy coding!
Scripts:
getUptime.py
# >>python getUptime.py OR >>python getUptime.py 127.0.0.1
import sys
import socket
import struct
if len(sys.argv) < 2: # if an arguement isn't included
host = 'localhost' # default to localhost
else:
host = sys.argv[1] # otherwise use the first argument
port = 2001 # default OptoMMP port number
# create socket with IPv4 family, and TCP
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# use that socket connect to host:port tuple
s.connect((host, port))
# build uptime block request: F0 30 01 0C <- uptime location in hex
myBytes = [0, 0, 4, 80, 0, 0, 255, 255, 240, 48, 1, 12, 0, 4, 0,0]
# send request and save the response
nSent = s.send(bytearray(myBytes)) # want nSent to be exactly 16 bytes
data = s.recv(24) # read response block is 24 bytes
data_block = data[16:20] # data_block is in bytes 16-19 for Read Response, stop at 20.
# decode bytearray in big-endian order (>) for integer value (i)
output = str(struct.unpack_from('>i', bytearray(data_block)))
# clip out first `(` and last two characters `,)` before printing
print 'uptime: ' + output[1:-2] + 'ms'
# close the socket:
s.close()
readModCh.py
# readModCh.py >>python readModCh.py <module #> <channel #>
import sys
import socket
import struct
host = '127.0.0.1' # groov EPIC IP
if(len(sys.argv) != 3): # If the module and/or channel are not provided.
print 'Please provide module # and channel #.'
print 'Exiting script . . .'
exit() # Inform the user and exit the script.
port = 2001 # default OptoMMP port number
tcode = 5 # read block request
modN = int(sys.argv[1]) # first argument
chN = int(sys.argv[2]) # second argument
# create socket with IPv4 family, and TCP
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# use that socket connect to host:port tuple
s.connect((host, port))
# Calculate the destination offset:
# EPIC digital read start address = 0xF01E0000
dest = 0xF01E0000 + (modN * 0x1000) + (chN * 0x40)
# build the read block request:
myBytes = [0, 0, (1 << 2), (tcode << 4), 0, 0, 255, 255, int(str(hex(dest))[2:4],16), int(str(hex(dest))[4:6],16), int(str(hex(dest))[6
:8],16), int(str(hex(dest))[8:10],16), 0, 4, 0, 0];
# send the read block request and save the response:
nSent = s.send(bytearray(myBytes)) # want nSent to be exactly 16 bytes
data = s.recv(20) # read block response is 16 + 4 bytes
data_block = data[16:20] # data_block is in bytes 16-19 for Read Response, stop at 20.
# decode bytearray in big-endian order (>) for integer value (i)
output = str(struct.unpack_from('>i', bytearray(data_block)))
# clip out first `(` and last two characters `,)` before printing
print 'module ' + str(modN) + ', point ' + str(chN) + ' = ' + output[1:-2]
#close the socket:
s.close()
writeModChVal.py
# writeModChVal.py >>python writeModChVal.py <module #> <channel #> <1|0>
import sys
import socket
import struct
host = '127.0.0.1' # groov EPIC IP
if(len(sys.argv) != 4): # If the module, channel, and/or value are not provided.
print 'Please provide module #, channel #, and value [1|0].'
print 'Exiting script . . .'
exit() # Inform the user and exit the script.
port = 2001 # default OptoMMP port number
tcode = 1 # write block request
modN = int(sys.argv[1]) # first argument
chN = int(sys.argv[2]) # second argument
val = int(sys.argv[3]) # third argument
# create socket with IPv4 family, and TCP
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# use that socket connect to host:port tuple
s.connect((host, port))
# Calculate the destination offset:
# EPIC digital write start address = 0xF0220000
dest = 0xF0220000 + (modN * 0x1000) + (chN * 0x40)
# build the write block request:
myBytes = [0, 0, (1 << 2), (tcode << 4), 0, 0, 255, 255, int(str(hex(dest))[2:4],16), int(str(hex(dest))[4:6],16), int(str(hex(dest))[6:8],16), int(str(hex(dest))[8:10],16), 0,16, 0,0, 0,0,0,val] + [0]*12;
# + [0]*12 to fill the write block and make it the correct size.
# send the write block request and save the response:
nSent = s.send(bytearray(myBytes)) # want nSent to be exactly 32 bytes
data = s.recv(12) # write block response is 12 bytes
data_block = data[4:8] # data_block is in bytes 4-7 for Write Response, stop at 8.
# decode bytearray in big-endian order(>) for integer value (i)
output = str(struct.unpack_from('>i', bytearray(data_block)))
# clip out first `(` and last two characters `,)` to get the status code number
status = int(output[1:-2])
if (status == 0):
print 'Write success ' + str(status)
else:
print 'Write failure ' + str(status)
#close the socket:
s.close()
Happy coding!