If you ever need some values that are not connected to the rest API, you may want to turn to the Opto Memory Mapped Protocol, OptoMMP, and look at the values exactly where they’re stored.
(All supported REST endpoints are listed in the references on the developer site)
In this post I’ll look at three memory mapped addresses that cover three very different pieces of data. I’ll show how to read them in, which functions I used, and how I chose to consume and process this data:
0xF030014C = device serial number (standard 32-bit integer)
0xF0300140 = analog scan time (float / decimal number)
0xF030002E = MAC address (64-bit unsigned integer to be converted to a string)
If you want to know where I found these addresses, the data types, or more information about how they’re decoded please check out the OptoMMP Protocol Guide, and if you’re well-versed C++ you may benifit from checking out the C++ toolkit source code. The entirety of this post is thanks to those two downloads (and a LOT of Googling).
Disclaimer: I am not an expert when it comes to coding in C++, but I’m always eager to learn more and I’d love to improve these sample programs. If you know of any ways to improve the code I use below, please post it in this thread!
Getting a groov Serial Number
I’ll start with the easier of the three and have a look at getting the serial number at 0xF030014C
. Since this is a standard integer value it’s pretty straightforward to unpack – there’s even a built-in function with the OptoMMP toolkit to do it:
int ReadBlockAsIntegers(uint32_t dwDestOffset, uint16_t wDataLength, int * pnData)
I’ll quickly go through this first line so it’s clear what we’re working with.
This function “ReadBlockAsIntegers” is a function that returns an integer (shown by the first int
) with a status value, and it requires the OptoMMP address (dwDestOffset
), the amount of data to fetch (wDataLength
), and a variable to put it in (* pnData
). It’s also important to match datatypes, as you will see in the sample code below.
The source code for this function can be found in the OptoMMP C++ toolkit: Opto22 - PAC-DEV-OPTOMMP-CPLUS - C++ OptoMMP Software Development Kit for <em>groov </em>EPIC, <em>groov </em>RIO, and SNAP PAC)
If we call this function with the OptoMMP address for the serial number, we’ll get back an integer number in the pnData
array at the starting index - pnData[0]
– that’s what we’re looking for.
Here’s what the main code looks like (you’ll need some other setup lines for this to run):
uint32_t MMP = 0xF030014C; // MMP address for the serial number
uint16_t length = sizeof(int); // reading one integer
int data[1]; // array to hold the single returned number
int nResult = EPIC.ReadBlockAsIntegers(MMP, length, data);
int serialNumber = data[0];
nResult is a number that says whether the function call was successful or not – in critical applications you can run a check on this to make sure it worked before running further code. I’ll skip that for this example.
After getting that serialNumber
from data[0]
I can now do whatever I want with it. In the sample code attached here I just print it to the console, but you can use this code however you like!
getSerialNumber.zip (70.7 KB)
Getting Analog Scanner Scan Time
The next function is pretty similar, except instead of reading the device serial number, it fetches the analog scanner scan time as a floating point / decimal number.
int ReadFloat(uint32_t dwDestOffset, float * pfValue);
As with the function above, it’s important to match your datatypes, which is shown in the sample code below. Again we have the MMP address, and a pointer to the variable that should hold the response.
uint32_t MMP = 0xF0300140; // MMP address for the analog scanner time
float value; // result value to hold the analog scanner time
int nResult = EPIC.ReadFloat(MMP, &value);
In this case you can just refer to value
as a float in the following code and it will hold the decimal number of milliseconds it takes for the analog scanner to run through its cycle.
Check out the sample code attached here if you want to try it with your own system:
getAnalogScanTime.zip (70.6 KB)
Getting (and decoding) MAC Address
Finally, we have the tricky one… the MAC address.
If you just want to use the code, I’ll link it right here, but if you’re interested about how it works, keep reading.
MAC Address TL;DR - The incoming value is an unsigned 64-bit integer, but we’re used to a format more like AA:00:22:00:XX:YY
and that has letters and punctuation, not just numbers… so there’s a lot of converting… here’s the code: getMACAddress.zip (84.7 KB)
. . .
So how does it work?
First, I read in the raw 64 bits (8 bytes) that should fill an unsigned 64-bit integer, then take the 2’s compliment of those bytes, then convert that number to it’s hex string. (Stack Overflow links will be at the end of the post . . . . )
Everything I do after that is formatting turn line 1 into line 2:
1> 0xa03d043aeac0a8
2> 00:A0:3D:04:3A:EA
I’ll only post the first part that collects and specifies the MAC address 00a03d043aea
, the rest of the formatting is optional, and can be found in the code attached below this code:
uint32_t MMP = 0xF030002E; // address for the MAC address
int length = 8; // MAC address is 64 bits = 8 bytes
uint8_t response[8]; // variable to hold the eight bytes
int nResult = EPIC.ReadBytes(MMP, length, response);
// convert the 8 bytes into an unsigned, 2's compliment 64-bit integer
uint64_t value = ((uint64_t)response[0] << 56 |
(uint64_t)response[1] << 48 |
(uint64_t)response[2] << 40 |
(uint64_t)response[3] << 32 |
(uint64_t)response[4] << 24 |
(uint64_t)response[5] << 16 |
(uint64_t)response[6] << 8 |
(uint64_t)response[7]);
std::stringstream buffer; // buffer to hold the number represented as a hexidecimal string
buffer << std::setw(16) << std::setfill('0') << std::hex << value;
std::string hexString = buffer.str();
hexString = hexString.substr(0,12);
The full source code and program you can either compile or run yourself are in this zip:
getMACAddress.zip (84.7 KB)
– If you want to run these programs, please read this! –
In order to run these attached programs you will need both secure shell access (GROOV-LIC-SHELL) and the root user password. To run these programs just type
./getSerialNumber
in the folder where the file is held, but first you will need to make the file itself executable by runningsudo chmod +x getSerialNumber
otherwise it will not work.
As always with shell access, proceed with caution, and respectsudo
!
With all that said, here are my Stack Overflow sources for those that want to dig a bit deeper into these details, and as always I welcome feedback and improvements on this code.
If you end up using this, I’d love to know where, and if you see any issues, please point them out in the thread below. And as always - happy coding!
- OptoMMP Protocol Guide - Definitely read this!
- OptoMMP C++ Library Source Code - required to run, very useful to read
- Stack Overflow: How to convert byte array into integer - shows 2’s compliment / endian reordering
- Stack Overflow: getting cout to a string - critical for saving a manipulated hex string