secsgem icon indicating copy to clipboard operation
secsgem copied to clipboard

Clarification on how to use custom functions

Open alanchev opened this issue 2 years ago • 3 comments

Dear Benjamin, foremost, thank you for providing this library. I'd go nuts trying to de- and encode GEM communications otherwise.

However, I've run into troubles properly defining a S02F49 (Enhanced Remote Command) message in accordance to your documentation and the examples as seen, for example, in secs/functions/s06f08.py.

Ultimately, I am to send a message of the following format (from the equipment's manual):

S2F49 W
<L[4/1]
	<U1[1/1] 12>
	<A[7/1] "enhtest">
	<A[7/1] "ADD-JOB">
	<L[3/1]
		<L[2/1]
			<A[5/1] "JobID">
			<A[5/1] "job50">
		>
		<L[2/1]
			<A[10/1] "RecipeName">
			<A[10/1] "transfer_1">
		>
		<L[2/1]
			<A[9/1] "Transfers">
			<L[25/1]
				<U1[4/1] 1 1 2 1>
				<U1[4/1] 2 4 4 2>
				<U1[4/1] 1 2 3 4>
			>
		>
	>
>

To achieve that, I've tried to define a custom message:

# SecsS02F49.py

from secsgem.secs.data_items import DataItemBase
from secsgem.secs.variables import Array
from secsgem.secs.functions import SecsStreamFunction
from secsgem.secs.data_items import DATAID, OBJSPEC, RCMD, CPNAME, CPVAL

class TRANSFERS(DataItemBase):
    __type__ = Array

class SecsS02F49(SecsStreamFunction):
        _stream = 2
        _function = 49
        _to_host: False
        _to_equipment: True
        _data_format = [
            DATAID,                 # U1; purpose unknown
            OBJSPEC,                # A; "ENH" purpose unknown
            RCMD,                   # A; static "ADD-JOB"
            [
                [
                    "JOBID",        # name of the list
                    CPNAME,         # A; static "JobID"
                    CPVAL           # A; job_id
                ],
                [
                    "RECIPE",       # name of the list
                    CPNAME,         # A; static "RecipeName"
                    CPVAL           # A; recipe_name 
                ],
                # [
                #     CPNAME,       # A; static "Transfers"
                #     TRANSFERS     # CEPVAL "o00" (List of U1?)
                # ]
            ]
        ]

and then to instatiate this class according to the examples I've seen earlier:

# main.py

import secsgem.gem
from SecsS02F49 import SecsS02F49

gem = secsgem.gem.GemHandler(
    address="127.0.0.1",
    port=5000,
    active=True,
    session_id=0,
    name="test"
)

f = SecsS02F49({
    "DATAID": 12,
    "OBJSPEC": 'enhtest',
    "RCMD": 'ADD-JOB',
    "JOBID": [{"CPNAME": 'JobID', "CPVAL": 'job42'}],
    "RECIPE": [{"CPNAME": 'RecipeName', "CPVAL": 'transfer_42'}]
})

print(f)

The above code yields a KeyError at secs/variables/list_type.py:214, as the field_name 'JOBID' is being looked for in self.data, which looks to me like a root object defined from my definition of _data_format:

> special variables
> function variables
> 'DATAID': <U1 12 >
> 'OBJSPEC': <A "enhtest">
> 'RCMD': <A "ADD-JOB">
> 'DATA': <L [2]
> len(): 4

What that ultimately tells me, is that I'm misunderstanding the examples I've seen, and that I'm either incorrectly defining _data_format, or I'm passing a malformed dictionary instantiating SecsS02F49 - or both.

To add to the confusion, if you remove the second list from _data_format:

# SecsS02F49.py
# ...

        _data_format = [
            DATAID,                 # U1; purpose unknown
            OBJSPEC,                # A; "ENH" purpose unknown
            RCMD,                   # A; static "ADD-JOB"
            [
                [
                    "JOBID",        # name of the list
                    CPNAME,         # A; static "JobID"
                    CPVAL           # A; job_id
                ],
                # [
                #     "RECIPE",       # name of the list
                #     CPNAME,         # A; static "RecipeName"
                #     CPVAL           # A; recipe_name 
                # ],
                # [
                #     CPNAME,       # A; static "Transfers"
                #     TRANSFERS     # CEPVAL "o00" (List of U1?)
                # ]
            ]
        ]

and consequently remove the corresponding line from the dictionary passed into the constructor:

# main.py
# ...

f = SecsS02F49({
    "DATAID": 12,
    "OBJSPEC": 'enhtest',
    "RCMD": 'ADD-JOB',
    "JOBID": [{"CPNAME": 'JobID', "CPVAL": 'job42'}],
    # "RECIPE": [{"CPNAME": 'RecipeName', "CPVAL": 'transfer_42'}]
})

print(f)

The code runs just fine, resulting in the expected output:

S2F49
  <L [4]
    <U1 12 >
    <A "enhtest">
    <A "ADD-JOB">
    <L [1]
      <L [2]
        <A "JobID">
        <A "job42">
      >
    >
  > .

I hope it'll be no trouble for you to spot what I'm dreadfully missing, and I'd be very grateful if you could point me towards a correct implementation.

Cheers from Germany, Alexander

P.S.: The described issue has been encountered with both the latest release version 0.1.0 and the latest development version (main branch of this repository as of today); the code above is attuned to the latter.

alanchev avatar Aug 16 '22 12:08 alanchev

@alanchev Not sure if this fixes your problem, but the colons in this snippet is wrong. I guess you want to replace them by an equals sign.


class SecsS02F49(SecsStreamFunction):
        _stream = 2
        _function = 49
        _to_host: False
        _to_equipment: True

twmr avatar Apr 04 '23 16:04 twmr

@thisch Now that you mention it, it is strikingly odd the runtime environment wouldn't raise an issue out of this syntax error. However, that was not the problem, the issue still resides in how to instantiate more complex objects. If I recall correctly, more fiddling surfaced that my issue most likely was about instantiating objects vs. lists incorrectly. Ultimately, the problem at hand got solved on a completely different path, and I pursued this issue no further. This issue might as well be closed or remain open for future discussion, in case anyone else stumbles across the same troubles.

alanchev avatar Apr 05 '23 07:04 alanchev

Ultimately, the problem at hand got solved on a completely different path, and I pursued this issue no further.

Anyway, I've figured out how you can achieve what you wanted:


class SecsS02F49(SecsStreamFunction):
        _stream = 2
        _function = 49
        _to_host = False
        _to_equipment = True
        _data_format = [
            DATAID,                 # U1; purpose unknown
            OBJSPEC,                # A; "ENH" purpose unknown
            RCMD,                   # A; static "ADD-JOB"
            [
                [
                    "JOBID",        # name of the list
                    CPNAME,         # A; static "JobID"
                    CPVAL           # A; job_id
                ],
                [
                    "RECIPE",       # name of the list
                    CPNAME,         # A; static "RecipeName"
                    CPVAL           # A; recipe_name
                ],
                # [
                #     CPNAME,       # A; static "Transfers"
                #     TRANSFERS     # CEPVAL "o00" (List of U1?)
                # ]
            ]
        ]

print(SecsS02F49)

f = SecsS02F49({
    "DATAID": 12,
    "OBJSPEC": 'enhtest',
    "RCMD": 'ADD-JOB',
    "DATA": {
         "JOBID": {"CPNAME": 'JobID', "CPVAL": 'job42'},
         "RECIPE": {"CPNAME": 'RecipeName', "CPVAL": 'transfer_42'},
    }
})

print(f.get())

twmr avatar Mar 27 '24 07:03 twmr

Thank you, twmr! Unfortunately I will not be able to test your solution anytime soon, but it does look sensible. I hope others who need this question answered will come across your answer. ... Or, you know, some AI will learn from it and pass on the knowledge.

Either way, this issue will be closed now, as to not remain open forever.

alanchev avatar Jul 19 '24 08:07 alanchev