LORD-MicroStrain / MSCL

MicroStrain Communication Library
https://www.microstrain.com/software/mscl
MIT License
76 stars 57 forks source link

Error when trying to set EventTriggerMode with CV7 (Python) #353

Open jhgoglio opened 1 year ago

jhgoglio commented 1 year ago

Hello,

I'm attempting to code up the "Gated Data Streaming using a GPIO" app note: https://s3.amazonaws.com/files.microstrain.com/CV7+Online/user_manual_content/app_notes/Example%20Gated%20Streaming.htm My issue is that I'm unable to program CV7 Events using Python.

For one reason or another, I am unable to set the trigger mode to ENABLED. I receive an error 3 (invalid parameter) when I try. I will paste relevant portions of my code below and the resulting error message. I have already set the GPIO (as in the example) using SensorConnect and saved this as the default startup config for my CV7.

params_trigger_gpio = mscl.EventTriggerGpioParameter params_trigger_gpio.mode = 2 params_trigger_gpio.pin = 3

params_trigger = mscl.EventTriggerParameters params_trigger.gpio = params_trigger_gpio

config_trigger = mscl.EventTriggerConfiguration config_trigger.instance = 1 config_trigger.trigger = 1 config_trigger.parameters = params_trigger

except mscl.Error as e1: print("MSCL error: {0}".format(e1)) except RuntimeError as e2: print("Runtime Error: {0}".format(e2)) except OSError as e3: print("OS error: {0}".format(e3)) except ValueError: print("An error occurred in parsing the data.")

try: connection = mscl.Connection.Serial(COM_PORT, BAUD_RATE) node = mscl.InertialNode(connection) if DO_PING: success = node.ping() print(success) print(node.getEventTriggerConfig(2).trigger) print(node.getEventTriggerConfig(2).parameters.gpio.mode) print(node.getEventTriggerConfig(2).parameters.gpio.pin) print(node.getEventTriggerMode(1)) node.setEventTriggerMode(1, 1) #0=DISABLED,1=ENABLED,2=TEST,3=TEST_PULSE print(node.getEventTriggerMode(1))

msclissa commented 1 year ago

You set up the configuration object, but will need to actually apply it to the device before enabling the trigger - a trigger with type 0 (None) cannot be enabled.

If you call setEventTriggerConfig() before setEventTriggerMode() it should work fine:

node.setEventTriggerConfig(config_trigger)
node.setEventTriggerMode(1, 1) #0=DISABLED,1=ENABLED,2=TEST,3=TEST_PULSE

If you were to set the event trigger type back to 0 (None) the trigger mode would be automatically reset back to 0 (Disabled) as well. Here is the output of a quick test function I wrote to demonstrate this behavior:

getEventTriggerConfig(1).trigger: 0
getEventTriggerMode(1)          : 0
setEventTriggerMode(1, 1).......failed: The EventControl command has failed. (Error Code: 3)
getEventTriggerMode(1)          : 0

Config trigger, set trigger type: 1
getEventTriggerConfig(1).trigger: 1
getEventTriggerMode(1)          : 0
setEventTriggerMode(1, 1).......succeeded
getEventTriggerMode(1)          : 1

Clear trigger, set trigger type: 0
getEventTriggerConfig(1).trigger: 0
getEventTriggerMode(1)          : 0
setEventTriggerMode(1, 1).......failed: The EventControl command has failed. (Error Code: 3)
getEventTriggerMode(1)          : 0

I hope this helps! Let me know if anything is unclear or continues to give you trouble!

jhgoglio commented 1 year ago

Thank you! That does make sense. I wasn't sure which to put first, so I've been switching them around.

Unfortunately, even when I execute setTriggerConfig(config_trigger) first, there is still something that I'm missing. I receive an error message that indicates that my EventTriggerConfiguration (variable name config_trigger) is somehow not properly set up:

TypeError: in method 'InertialNode_setEventTriggerConfig', argument 2 of type 'mscl::EventTriggerConfiguration'

My suspicion is this portion of my code: params_trigger = mscl.EventTriggerParameters params_trigger.gpio = params_trigger_gpio but I have no reasoning behind this, besides the apparent fact that the constructor is defaulted to 'combined' mode.

Other things that I've tried to fix the error: I've also tried to explicitly typecast any numerical properties (like pin, instance, etc) into int() and hex() to no avail. I've also tried, for any ENUM-based properties, to replace ints with appropriate ENUMs (via the class definitions in mscl.py and via _mscl.pyd directly).

Thanks for your help!

jhgoglio commented 1 year ago

Would you mind sending me your python script, so that I can test it on my system? This way, I can eliminate the possibility that my particular code is to blame for this issue.

jhgoglio commented 1 year ago

One other interesting thing to note:

I tried to run a getTriggerConfig() and pass this EventTriggerConfiguration object off to the setTriggerConfig() function and this did work. The setTriggerMode() still failed, due to the trigger configuration still being 0. However, this does strongly indicate to me that there is something wrong with the config_trigger object that I built up in my code. I just can't figure out what precisely is wrong.

config = node.getTriggerConfig()
node.setTriggerConfig(config)        # This works! My config_trigger fails for some reason
setTriggerMode(1,1)                  # Still fails, due to config.trigger = 0, or another non-viable default setting
msclissa commented 1 year ago

Sorry for the delay!

I ran my test in C++, as far as the API goes the calls I made were identical to what you are using. I am about to do some testing with Python. The error you're getting is not an MSCL-specific error and I'm concerned something has gone wrong with building the Python interface for the event config data structures. I will get back to you with an update later today or tomorrow. Apologies for the confusion!

jhgoglio commented 1 year ago

Got it. I was able to check out the mscl.py file and nothing looked way off from within that file.

Just FYI: I saw you guys just released a Python 3.11 wrapper (Thank you!). I tried that one out, just in case, but unfortunately got the same result. I'll also be trying out the NET DLL (for LabVIEW) rather soon and will let you know how things go there.

Thanks again

msclissa commented 1 year ago

I'm seeing the same error you're seeing with Python, regardless of version. I haven't tried .NET yet, but it's definitely possible there will be the same issue. MSCL is written in C++ and then we use a tool to generate Python and .NET interfaces for it - the EventTriggerParameters object has a different underlying data structure that we hadn't used before so I guess we didn't get it to generate the interface properly.

Hopefully that will be straightforward to resolve, but in the meantime I am putting together a function to use as a workaround. I'll post that by the end of tomorrow. Thanks for your patience!

msclissa commented 1 year ago

The following function can be used instead of InertialNode::setEventTriggerConfig(). Everything can be set up the same way you have it already and the workaround function will turn those objects into the raw command bytes and send it using the generic InertialNode::doCommand() function. I've also made a function to use instead of InertialNode::setEventActionConfig() for when you want to configure actions.

These will work for configuring triggers and actions - if you want to read the configuration back out from the device you'll probably run into similar issues accessing the parameters details. You should be able to access the top-level info on the EventTriggerConfiguration and EventActionConfiguration objects (instance, type, trigger). If you need this functionality let me know.

There are examples below for how to use each, let me know if you run into any issues!

Example: Configure Trigger

connection = mscl.Connection.Serial("COM33", 115200)
node = mscl.InertialNode(connection)

triggerParams = mscl.EventTriggerGpioParameter()
triggerParams.mode = 2
triggerParams.pin = 3

triggerConfig = mscl.EventTriggerConfiguration()
triggerConfig.instance = 1

# double check this matches the params type
triggerConfig.trigger = mscl.EventTriggerConfiguration.GPIO_TRIGGER

# replace mscl call: node.setEventTriggerConfig(triggerConfig)
setEventTriggerConfig(node, triggerConfig, triggerParams)

Example: Configure Action

connection = mscl.Connection.Serial("COM33", 115200)
node = mscl.InertialNode(connection)

actionParams = mscl.EventActionMessageParameter()

# set sample rate
actionParams.sampleRate = mscl.SampleRate.Hertz(1)

# set output channels
fields = mscl.MipChannelFields()
fields.push_back(mscl.MipTypes.CH_FIELD_SENSOR_EULER_ANGLES)
fields.push_back(mscl.MipTypes.CH_FIELD_SENSOR_SCALED_GYRO_VEC)
fields.push_back(mscl.MipTypes.CH_FIELD_SENSOR_SCALED_ACCEL_VEC)
actionParams.setChannelFields(mscl.MipTypes.CLASS_AHRS_IMU, fields)

actionConfig = mscl.EventActionConfiguration()
actionConfig.instance = 1
actionConfig.trigger = 1

# double check this matches the params type
actionConfig.type = mscl.EventActionConfiguration.MESSAGE

# replace mscl call: node.setEventActionConfig(actionConfig)
setEventActionConfig(node, actionConfig, actionParams)

setEventTriggerConfig()

import struct

def setEventTriggerConfig(_node, _triggerConfig, _triggerParams = 0):
    values = mscl.Bytes()

    # include function selector: write
    values.push_back(1)

    values.push_back(_triggerConfig.instance)
    values.push_back(_triggerConfig.trigger)

    if _triggerConfig.trigger == mscl.EventTriggerConfiguration.GPIO_TRIGGER:
        values.push_back(_triggerParams.pin)
        values.push_back(_triggerParams.mode)
    elif _triggerConfig.trigger == mscl.EventTriggerConfiguration.THRESHOLD_TRIGGER:
        chfield = bytearray(struct.pack(">H", _triggerParams.channelField()))
        for b in chfield:
            values.push_back(b)

        values.push_back(_triggerParams.channelIndex())
        values.push_back(_triggerParams.type)

        thresholdLow = bytearray(struct.pack(">d", float(_triggerParams.lowThreshold)))
        for b in thresholdLow:
            values.push_back(b)

        thresholdHigh = bytearray(struct.pack(">d", float(_triggerParams.highThreshold)))
        for b in thresholdHigh:
            values.push_back(b)
    elif _triggerConfig.trigger == mscl.EventTriggerConfiguration.COMBINATION_TRIGGER:
        logicTable = bytearray(struct.pack(">H", _triggerParams.logicTable))
        for b in logicTable:
            values.push_back(b)

        for inputTrigger in _triggerParams.inputTriggers:
            values.push_back(inputTrigger)

    cmd = bytearray(struct.pack(">H", mscl.MipTypes.CMD_EVENT_TRIGGER_CONFIGURATION))    
    node.doCommand(cmd[0], cmd[1], values, True, False)

setEventActionConfig()

import struct

def setEventActionConfig(_node, _actionConfig, _actionParams = 0):
    values = mscl.Bytes()

    # include function selector: write
    values.push_back(1)

    values.push_back(_actionConfig.instance)
    values.push_back(_actionConfig.trigger)
    values.push_back(_actionConfig.type)

    if _actionConfig.type == mscl.EventActionConfiguration.GPIO:
        values.push_back(_actionParams.pin)
        values.push_back(_actionParams.mode)

    elif _actionConfig.type == mscl.EventActionConfiguration.MESSAGE:
        values.push_back(_actionParams.dataClass())

        decimationVal = _actionParams.sampleRate.toDecimation(node.getDataRateBase(_actionParams.dataClass()))
        decimation = bytearray(struct.pack(">H", decimationVal))
        for b in decimation:
            values.push_back(b)

        fields = _actionParams.getChannelFields()
        fields = node.features().filterSupportedChannelFields(fields)

        values.push_back(len(fields))
        for field in fields:
            fieldId = bytearray(struct.pack(">H", field))
            values.push_back(fieldId[1])

    cmd = bytearray(struct.pack(">H", mscl.MipTypes.CMD_EVENT_ACTION_CONFIGURATION))    
    node.doCommand(cmd[0], cmd[1], values, True, False)
jhgoglio commented 1 year ago

Sorry for my late reply. I just now had a chance to use the workaround on my setup; it works great!

I'll keep an eye on the git repo, but if possible please let me know when you guys find out what went wrong with the SWIG binding. I'll want to move to using the NET interface once that is fixed.

Thanks again!

jhgoglio commented 11 months ago

Hi guys,

Any update on the wrapper fix? I'm still able to use the workaround, but hoping to soon be able to use the more direct method. Thanks!

msclissa commented 10 months ago

Unfortunately no update on this yet, sorry for the delay!