diff --git a/cfecfs/missionlib/CMakeLists.txt b/cfecfs/missionlib/CMakeLists.txt index 449c4bcad..17cb6f5ba 100644 --- a/cfecfs/missionlib/CMakeLists.txt +++ b/cfecfs/missionlib/CMakeLists.txt @@ -1,5 +1,6 @@ # # LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation +# LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library # # Copyright (c) 2020 United States Government as represented by # the Administrator of the National Aeronautics and Space Administration. @@ -173,4 +174,5 @@ endif (SUPPORTS_SHARED_LIBS) add_subdirectory(fsw) add_subdirectory(lua) +add_subdirectory(python) diff --git a/cfecfs/missionlib/python/CMakeLists.txt b/cfecfs/missionlib/python/CMakeLists.txt new file mode 100644 index 000000000..ae482cfef --- /dev/null +++ b/cfecfs/missionlib/python/CMakeLists.txt @@ -0,0 +1,170 @@ +# +# LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library +# +# Copyright (c) 2020 United States Government as represented by +# the Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +######################################################### +# CFE_MISSIONLIB PYTHON BINDINGS +######################################################### +# +# This code works with both Python 2 and Python 3.x +# +# Version 3.x is preferred. +# +# However some old software may only provide a Python2.7 module +# and as such this needs to be built with version 2.7 for that. +# +# In order to accommodate this the MISSIONLIB_PYTHON_SEARCH_VERSION +# should be defined in the configuration somewhere (toolchain etc) +# which in turn is passed to pkg-config to find Python. +# +# Alternatively, the toolchain can also directly specify where +# Python headers/libs can be found by setting cachevars directly: +# PYTHON_FOUND = TRUE +# PYTHON_INCLUDE_DIRS = directory containing Python headers +# PYTHON_LIBS = libraries to put on link line (-l switches) +# + +project(CFE_MISSIONLIB_PYTHONBINDINGS C) + +if(NOT PYTHON_FOUND) + + # By default, try to find Python3>=3.4 if no version specified... + # This simplifies building as a simple local library. + + # But do not assume this default when cross-compiling or building CFS, + # because if the toolchain isn't configured correctly, searching could + # find the wrong version and break the build + # (in particular, pkg-config might find the host version instead of + # the cross version and try to use it) + + # In any sort of cross-compile or CFS build, then the user needs to + # explicitly do something to enable Python bindings. + if (NOT DEFINED CFE_MISSIONLIB_PYTHON_SEARCH_VERSION AND + NOT CMAKE_CROSSCOMPILING AND + NOT IS_CFS_ARCH_BUILD) + set(CFE_MISSIONLIB_PYTHON_SEARCH_VERSION python3>=3.4) + endif() + + if (CFE_MISSIONLIB_PYTHON_SEARCH_VERSION) + find_edslib_dependency(PYTHON Python.h ${CFE_MISSIONLIB_PYTHON_SEARCH_VERSION}) + endif (CFE_MISSIONLIB_PYTHON_SEARCH_VERSION) + +endif(NOT PYTHON_FOUND) + +# The final decision on whether to build the standalone module +# should be made by the user or parent build script. +option(CFE_MISSIONLIB_PYTHON_BUILD_STANDALONE_MODULE + "Build a generic Python module that can be imported into standalone Python code" + OFF) + +# If not found then this module cannot be built, however +# this is an optional feature therefore not a fatal error. +if (NOT PYTHON_FOUND) + message(" Python support not configured, skipping MissionLib Python bindings") +else () + include_directories(inc) + include_directories(${EDSLIB_FSW_SOURCE_DIR}/inc) + include_directories(${EDSLIB_PYTHONBINDINGS_SOURCE_DIR}/inc) + include_directories(${EDSLIB_PYTHONBINDINGS_SOURCE_DIR}/src) + include_directories(${EDS_CFECFS_MISSIONLIB_FSW_SOURCE_DIR}/inc) + include_directories(${EDS_CFECFS_MISSIONLIB_FSW_SOURCE_DIR}/src) + include_directories(${PYTHON_INCLUDE_DIRS}) + include_directories(${global_MISSION_DIR}/inc) + + set(CFE_MISSIONLIB_PYTHON_SOURCE_LIST + src/cfe_missionlib_python_database.c + src/cfe_missionlib_python_interface.c + src/cfe_missionlib_python_topic.c + src/cfe_missionlib_python_setup.c + ) + + # + # The "static" library target is always defined but will only be built on demand + # it should work pretty much the same with any build (with or without CFS) + # + # This static library is used when building a custom Python interpreter executable + # that includes the EdsLib module built-in. (Otherwise Python would typically use + # a dynamic module with the "import" logic instead - see the "objects" target instead) + # + add_library(cfe_missionlib_python_static STATIC EXCLUDE_FROM_ALL + ${CFE_MISSIONLIB_PYTHON_SOURCE_LIST}) + target_link_libraries(cfe_missionlib_python_static + ${PYTHON_LIBRARIES}) + + # + # The "cfe_missionlib_python_pic" library target is the basis for a dynamically-loaded + # Python modules. + # + # This isn't directly built as a complete module here because this code does + # not include an initialization/entry point function. Such a function needs to + # be supplied based on the environment it is being loaded into. + # + # This is compiled as position-independent code (PIC) if the toolchain supports it + # (if not, then the static version would be used and this isn't needed anyway) + # + # Note that the target will be an archive but contain PIC code, so it can be + # linked into a shared library or module. + # + # - For CFE/CFS this is an init function that returns a CFE status code + # This init glue is supplied externally via a separate PyCFS code blob + # - For standalone Python then the interpreter has a specific init name it looks for + # This depends on whether it is Python 2 or 3 -- see edslib_python_module.c + # + # This approach allows one or both of these targets to be built while + # building the core logic only once, while still creating a single module + # file at runtime. + add_library(cfe_missionlib_python_pic STATIC EXCLUDE_FROM_ALL + ${CFE_MISSIONLIB_PYTHON_SOURCE_LIST}) + set_target_properties(cfe_missionlib_python_pic PROPERTIES + POSITION_INDEPENDENT_CODE TRUE) + #target_link_libraries(cfe_missionlib_python_pic cfe_missionlib_pic )#missionlib_runtime_pic) + + if (CFE_MISSIONLIB_PYTHON_BUILD_STANDALONE_MODULE) + + # + # Create a standalone Python module + # + # In this mode, the main executable/host environment is expected to be Python + # So this needs to generate a loadable module that is compatible with + # the Python "import" function. This binary needs to be named to match + # the python module name, and the file needs to be placed somewhere + # in the Python module path so it is found when running "import". + # + # The "cfe_missionlib_python_module.c" file provides a compatible init function. + # This is what Python looks for as it loads the module as part of "import" + # (The init function is a different signature depending on Python 2 or 3) + # + add_library(cfe_missionlib_python_module MODULE + src/cfe_missionlib_python_module.c + $ + $ + $) + + # Per Python naming conventions, the output file should be called only "CFE_MissionLib" + # to match the name of the module it defines. It will be installed into a + # Python-specific subdirectory to avoid name conflicts with the non-Python MissionLib. + set_target_properties(cfe_missionlib_python_module PROPERTIES PREFIX "" OUTPUT_NAME "CFE_MissionLib") + #target_link_libraries(cfe_missionlib_python_module cfe_missionlib_python_pic edslib_runtime_pic) + + install(TARGETS cfe_missionlib_python_module DESTINATION "lib/python") + + endif (CFE_MISSIONLIB_PYTHON_BUILD_STANDALONE_MODULE) + +endif() + diff --git a/cfecfs/missionlib/python/inc/cfe_missionlib_python.h b/cfecfs/missionlib/python/inc/cfe_missionlib_python.h new file mode 100644 index 000000000..98b635b20 --- /dev/null +++ b/cfecfs/missionlib/python/inc/cfe_missionlib_python.h @@ -0,0 +1,104 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_module.h +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey@nasa.gov +** +** Purpose: +** Header file for Python / MissionLib bindings +** +******************************************************************************/ + +#ifndef TOOLS_EDS_CFECFS_MISSIONLIB_PYTHON_INC_CFE_MISSIONLIB_PYTHON_H_ +#define TOOLS_EDS_CFECFS_MISSIONLIB_PYTHON_INC_CFE_MISSIONLIB_PYTHON_H_ + +#include +#include "cfe_missionlib_api.h" +//#include +//#include + +/** + * Documentation string for this Python module + * + * This is provided for applications that supply their own custom init routine. + */ +#define CFE_MISSIONLIB_PYTHON_DOC "Module which provides an interface to the CFE_MissionLib Runtime Library." + +/** + * Base Name of the Python module + * + * Applications should use this name if registering a custom inittab. + * It needs to be consistent because all module-supplied types use this + * as the base name, which carries through to the respective "repr()" + * implementation and other user-visible items. + */ +#define CFE_MISSIONLIB_PYTHON_MODULE_NAME "CFE_MissionLib" + +/** + * Get the name of a Python entity + * + * This macro converts an unqualified name to a qualified name, + * by adding a prefix of CFE_MISSIONLIB_PYTHON_MODULE_NAME. It is used + * by all types to keep the naming consistent. + */ +#define CFE_MISSIONLIB_PYTHON_ENTITY_NAME(x) CFE_MISSIONLIB_PYTHON_MODULE_NAME "." x + + +/** + * Main Initializer function that sets up a newly-minted module object + * + * This calls PyType_Ready() for all the Python type objects defined in + * this module, and adds the required members to the module, and returns + * the new module to the caller. + * + * This is almost (but not quite) usable as a Python module init function. + * Depending on the environment, additional members may need to be added, + * and the API/name might need to be adjusted (Python2 and Python3 have + * different naming and calling conventions). So it is expected that + * an additional wrapper around this will be added to accommodate this. + */ +PyObject* CFE_MissionLib_Python_CreateModule(void); + +/** + * Creates a Python SoftwareBus Interface Database object from a C Database Object + * + * This would typically be used when the DB object is statically linked + * into the C environment, and should be exposed to Python as a built-in. + */ +PyObject *CFE_MissionLib_Python_Database_CreateFromStaticDB(const char *Name, const CFE_MissionLib_SoftwareBus_Interface_t *Intf); + +/** + * Gets the C EDS Database object from a Python MissionLib Database Object + * + * This should be used when calling the MissionLib API from Python. + */ +const CFE_MissionLib_SoftwareBus_Interface_t *CFE_MissionLib_Python_Database_GetDB(PyObject *obj); + +/** + * Gets a topic entry from a Python CFE MissionLib Interface Database object using a C Msg_Id_t value + * + * This is a convenience wrapper that performs type checking and conversions + */ +//PyTypeObject *CFE_MissionLib_Python_TopicEntry_GetFromMsgId(PyObject *dbobj, const CFE_SB_SoftwareBus_PubSub_Interface_t Params); + +#endif /* TOOLS_EDS_CFECFS_MISSIONLIB_PYTHON_INC_CFE_MISSIONLIB_PYTHON_H_ */ diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_database.c b/cfecfs/missionlib/python/src/cfe_missionlib_python_database.c new file mode 100644 index 000000000..8c01843fd --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_database.c @@ -0,0 +1,635 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/****************************************************************************** +** File: cfe_missionlib_python_database.c +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey@nasa.gov +** +** Purpose: +** Implement python type which represents an CFE_MissionLib database +** +** Allows python code to dynamically load a database object and retrieve +** entries from it. +** +******************************************************************************/ + +#include "cfe_missionlib_python_internal.h" +#include +#include +#include "cfe_missionlib_runtime.h" + +#include "cfe_mission_eds_interface_parameters.h" +#include "ccsds_spacepacket_eds_typedefs.h" +#include "ccsds_spacepacket_eds_defines.h" +#include "cfe_sb_eds_typedefs.h" +#include "edslib_binding_objects.h" + +PyObject *CFE_MissionLib_Python_DatabaseCache = NULL; + +static void CFE_MissionLib_Python_Database_dealloc(PyObject * obj); +static PyObject * CFE_MissionLib_Python_Database_new(PyTypeObject *obj, PyObject *args, PyObject *kwds); +static PyObject * CFE_MissionLib_Python_Database_repr(PyObject *obj); +static int CFE_MissionLib_Python_Database_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_Database_clear(PyObject *obj); + +static PyObject * CFE_MissionLib_Python_Database_GetInterface(PyObject *obj, PyObject *key); +static PyObject * CFE_MissionLib_Python_DecodeEdsId(PyObject *obj, PyObject *args); +static PyObject * CFE_MissionLib_Python_Set_PubSub(PyObject *obj, PyObject *args); + +static PyObject * CFE_MissionLib_Python_Instance_iter(PyObject *obj); +static void CFE_MissionLib_Python_InstanceIterator_dealloc(PyObject * obj); +static int CFE_MissionLib_Python_InstanceIterator_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_InstanceIterator_clear(PyObject *obj); +static PyObject * CFE_MissionLib_Python_InstanceIterator_iternext(PyObject *obj); + +static PyMethodDef CFE_MissionLib_Python_Database_methods[] = +{ + {"Interface", CFE_MissionLib_Python_Database_GetInterface, METH_O, "Lookup an Interface type from DB."}, + {"DecodeEdsId", CFE_MissionLib_Python_DecodeEdsId, METH_VARARGS, "Decode the EdsID from a packed cFE message"}, + {"SetPubSub", CFE_MissionLib_Python_Set_PubSub, METH_VARARGS, "Set the PubSub parameters for a command message"}, + {NULL} /* Sentinel */ +}; + +static struct PyMemberDef CFE_MissionLib_Python_Database_members[] = +{ + {"Name", T_OBJECT_EX, offsetof(CFE_MissionLib_Python_Database_t, DbName), READONLY, "Database Name" }, + {NULL} /* Sentinel */ +}; + + +PyTypeObject CFE_MissionLib_Python_DatabaseType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("Database"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_Database_t), + .tp_dealloc = CFE_MissionLib_Python_Database_dealloc, + .tp_new = CFE_MissionLib_Python_Database_new, + .tp_methods = CFE_MissionLib_Python_Database_methods, + .tp_members = CFE_MissionLib_Python_Database_members, + .tp_repr = CFE_MissionLib_Python_Database_repr, + .tp_traverse = CFE_MissionLib_Python_Database_traverse, + .tp_clear = CFE_MissionLib_Python_Database_clear, + .tp_iter = CFE_MissionLib_Python_Instance_iter, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, + .tp_weaklistoffset = offsetof(CFE_MissionLib_Python_Database_t, WeakRefList), + .tp_doc = PyDoc_STR("Interface database") +}; + +PyTypeObject CFE_MissionLib_Python_InstanceIteratorType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("InstanceIterator"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_InstanceIterator_t), + .tp_dealloc = CFE_MissionLib_Python_InstanceIterator_dealloc, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = CFE_MissionLib_Python_InstanceIterator_traverse, + .tp_clear = CFE_MissionLib_Python_InstanceIterator_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = CFE_MissionLib_Python_InstanceIterator_iternext, + .tp_doc = PyDoc_STR("CFE MissionLib InterfaceIteratorType") +}; + +static int CFE_MissionLib_Python_Database_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_Database_t *self = (CFE_MissionLib_Python_Database_t *)obj; + Py_VISIT(self->DbName); + Py_VISIT(self->EdsDbObj); + Py_VISIT(self->TypeCache); + return 0; +} + +static int CFE_MissionLib_Python_Database_clear(PyObject *obj) +{ + CFE_MissionLib_Python_Database_t *self = (CFE_MissionLib_Python_Database_t *)obj; + Py_CLEAR(self->DbName); + Py_CLEAR(self->EdsDbObj); + Py_CLEAR(self->TypeCache); + return 0; +} + +static void CFE_MissionLib_Python_Database_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_Database_t *self = (CFE_MissionLib_Python_Database_t *)obj; + + Py_CLEAR(self->DbName); + Py_CLEAR(self->EdsDbObj); + Py_CLEAR(self->TypeCache); + + if (self->WeakRefList != NULL) + { + PyObject_ClearWeakRefs(obj); + self->WeakRefList = NULL; + } + + if (self->dl != NULL) + { + dlclose(self->dl); + self->dl = NULL; + } + + CFE_MissionLib_Python_DatabaseType.tp_base->tp_dealloc(obj); +} + +static CFE_MissionLib_Python_Database_t *CFE_MissionLib_Python_Database_CreateImpl(PyTypeObject *obj, PyObject *Name, const CFE_MissionLib_SoftwareBus_Interface_t *IntfDb, PyObject *EdsDb) +{ + CFE_MissionLib_Python_Database_t *self; + PyObject *weakref; + + self = (CFE_MissionLib_Python_Database_t*)obj->tp_alloc(obj, 0); + if (self == NULL) + { + return NULL; + } + + self->TypeCache = PyDict_New(); + if (self->TypeCache == NULL) + { + Py_DECREF(self); + return NULL; + } + + self->IntfDb = IntfDb; + Py_INCREF(EdsDb); + self->EdsDbObj = (EdsLib_Python_Database_t *)EdsDb; + Py_INCREF(Name); + self->DbName = Name; + //self->NumInterfaces = IntfDb->NumInterfaces; + + /* Create a weak reference to store in the local cache in case this + * database is constructed again. */ + weakref = PyWeakref_NewRef((PyObject*)self, NULL); + if (weakref == NULL) + { + Py_DECREF(self); + return NULL; + } + + PyDict_SetItem(CFE_MissionLib_Python_DatabaseCache, Name, weakref); + Py_DECREF(weakref); + + return self; +} + +//PyObject *CFE_MissionLib_Python_Database_CreateFromStaticDB(const char *Name, const CFE_MissionLib_SoftwareBus_Interface_t *IntfDb) +//{ +// PyObject *pyname = PyUnicode_FromString(Name); +// CFE_MissionLib_Python_Database_t *self; +// PyObject *weakref; +// +// if (pyname == NULL) +// { +// return NULL; +// } +// +// weakref = PyDict_GetItem(CFE_MissionLib_Python_DatabaseCache, pyname); +// if (weakref != NULL) +// { +// self = (CFE_MissionLib_Python_Database_t *)PyWeakref_GetObject(weakref); +// if (Py_TYPE(self) == &CFE_MissionLib_Python_DatabaseType) +// { +// if (self->IntfDb != IntfDb) +// { +// PyErr_SetString(PyExc_RuntimeError, "Database name conflict"); +// self = NULL; +// } +// else +// { +// Py_INCREF(self); +// } +// Py_DECREF(pyname); +// return (PyObject*)self; +// } +// } +// +// Py_DECREF(pyname); +// return (PyObject*)CFE_MissionLib_Python_Database_CreateImpl(&CFE_MissionLib_Python_DatabaseType, pyname, IntfDb, NULL); +//} + +const CFE_MissionLib_SoftwareBus_Interface_t *CFE_MissionLib_Python_Database_GetDB(PyObject *obj) +{ + if (obj == NULL) + { + return NULL; + } + if (!PyObject_TypeCheck(obj, &CFE_MissionLib_Python_DatabaseType)) + { + PyErr_SetObject(PyExc_TypeError, obj); + return NULL; + } + + return ((CFE_MissionLib_Python_Database_t*)obj)->IntfDb; +} + +static PyObject *CFE_MissionLib_Python_Database_new(PyTypeObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *arg1; + PyObject *arg2; + + PyObject *tempargs; + const char *dbstr; + PyObject *nameobj; + char *p; + char tempstring[512]; + void *handle; + void *symbol; + const char *errstr; + CFE_MissionLib_Python_Database_t *self; + PyObject *weakref; + + if (!PyArg_UnpackTuple(args, "Database_new", 1, 2, &arg1, &arg2)) + { + PyErr_Format(PyExc_RuntimeError, "Database arguments: Interface Database Identifier and EdsDb Python Object (If Database associated with Identifier has not been created"); + return NULL; + } + + if (PyUnicode_Check(arg1)) + { + tempargs = PyUnicode_AsUTF8String(arg1); + } + else + { + tempargs = PyObject_Bytes(arg1); + } + dbstr = PyBytes_AsString(tempargs); + + /* + * To avoid wasting resources, do not create the same database multiple times. + * First check if the db cache object already contains an instance for this name + * note: PyDict_GetItem returns borrowed reference + */ + weakref = PyDict_GetItemString(CFE_MissionLib_Python_DatabaseCache, (char*)dbstr); + if (weakref != NULL) + { + self = (CFE_MissionLib_Python_Database_t *)PyWeakref_GetObject(weakref); + if (Py_TYPE(self) == &CFE_MissionLib_Python_DatabaseType) + { + Py_INCREF(self); + return (PyObject*)self; + } + } + else if (arg2 == Py_None) + { + PyErr_Format(PyExc_RuntimeError, "EdsDb Python Object argument required"); + return NULL; + } + + snprintf(tempstring,sizeof(tempstring),"%s_eds_interfacedb.so", dbstr); + + /* Clear any pending dlerror value */ + dlerror(); + handle = dlopen(tempstring, RTLD_LOCAL|RTLD_NOW); + errstr = dlerror(); + + if (handle == NULL && errstr == NULL) + { + errstr = "Unspecified Error - NULL handle"; + } + + if (errstr != NULL) + { + if (handle != NULL) + { + dlclose(handle); + } + PyErr_SetString(PyExc_RuntimeError, errstr); + return NULL; + } + + snprintf(tempstring,sizeof(tempstring),"%s_SOFTWAREBUS_INTERFACE", dbstr); + p = tempstring; + while (*p != 0) + { + *p = toupper(*p); + ++p; + } + + symbol = dlsym(handle, tempstring); + if (symbol == NULL) + { + dlclose(handle); + PyErr_Format(PyExc_RuntimeError, "Symbol %s undefined", tempstring); + return NULL; + } + + nameobj = PyUnicode_FromString(dbstr); + if (nameobj == NULL) + { + dlclose(handle); + return NULL; + } + + self = CFE_MissionLib_Python_Database_CreateImpl(&CFE_MissionLib_Python_DatabaseType, nameobj, symbol, arg2); + Py_DECREF(nameobj); + if (self == NULL) + { + dlclose(handle); + return NULL; + } + + self->dl = handle; + + return (PyObject*)self; +} + +PyObject *CFE_MissionLib_Python_Database_repr(PyObject *obj) +{ + CFE_MissionLib_Python_Database_t *self = (CFE_MissionLib_Python_Database_t *)obj; + return PyUnicode_FromFormat("%s(%R)", obj->ob_type->tp_name, self->DbName); +} + +static PyObject *CFE_MissionLib_Python_Database_GetInterface(PyObject *obj, PyObject *arg) +{ + PyObject *tempargs = NULL; + PyObject *result = NULL; + + tempargs = PyTuple_Pack(2, obj, arg); + if (tempargs == NULL) + { + return NULL; + } + result = PyObject_Call((PyObject*)&CFE_MissionLib_Python_InterfaceType, tempargs, NULL); + Py_DECREF(tempargs); + + return result; +} + +static PyObject *CFE_MissionLib_Python_DecodeEdsId(PyObject *obj, PyObject *args) +{ + CFE_MissionLib_Python_Database_t *IntfDb = (CFE_MissionLib_Python_Database_t *)obj; + PyObject *arg1; + EdsLib_Python_Database_t *EdsDb; + + Py_ssize_t BytesSize; + char *NetworkBuffer; + CCSDS_SpacePacket_Buffer_t LocalBuffer; + + CFE_SB_SoftwareBus_PubSub_Interface_t PubSubParams; + CFE_SB_Publisher_Component_t PublisherParams; + CFE_SB_Listener_Component_t ListenerParams; + + EdsLib_Id_t EdsId; + uint16_t TopicId; + EdsLib_DataTypeDB_TypeInfo_t TypeInfo; + int32_t Status; + + PyObject *result = NULL; + + if (!PyArg_UnpackTuple(args, "DecodeEdsId", 1, 1, &arg1)) + { + PyErr_Format(PyExc_RuntimeError, "encoded bytes string argument expected"); + return NULL; + } + + Py_INCREF(arg1); + + do + { + EdsDb = IntfDb->EdsDbObj; + + if (!PyBytes_Check(arg1)) + { + PyErr_Format(PyExc_RuntimeError, "DecodeEdsId argument not of bytes string type"); + break; + } + BytesSize = PyBytes_Size(arg1); + PyBytes_AsStringAndSize(arg1, &NetworkBuffer, &BytesSize); + + EdsId = EDSLIB_MAKE_ID(EDS_INDEX(CCSDS_SPACEPACKET), CCSDS_TelemetryPacket_DATADICTIONARY); + Status = EdsLib_DataTypeDB_GetTypeInfo(EdsDb->GD, EdsId, &TypeInfo); + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Unable to get type info for CCSDS_SPACEPACKET: return status = %d", Status); + break; + } + + Status = EdsLib_DataTypeDB_UnpackPartialObject(EdsDb->GD, &EdsId, + LocalBuffer.Byte, NetworkBuffer, sizeof(LocalBuffer), 8*TypeInfo.Size.Bytes, 0); + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Unable to unpack partial object: return status = %d", Status); + break; + } + + CFE_SB_Get_PubSub_Parameters(&PubSubParams, &LocalBuffer.BaseObject); + + if (CFE_SB_PubSub_IsPublisherComponent(&PubSubParams)) + { + CFE_SB_UnmapPublisherComponent(&PublisherParams, &PubSubParams); + TopicId = PublisherParams.Telemetry.TopicId; + + Status = CFE_MissionLib_GetArgumentType(IntfDb->IntfDb, CFE_SB_Telemetry_Interface_ID, + PublisherParams.Telemetry.TopicId, 1, 1, &EdsId); + } + else if (CFE_SB_PubSub_IsListenerComponent(&PubSubParams)) + { + CFE_SB_UnmapListenerComponent(&ListenerParams, &PubSubParams); + TopicId = ListenerParams.Telecommand.TopicId; + + Status = CFE_MissionLib_GetArgumentType(IntfDb->IntfDb, CFE_SB_Telecommand_Interface_ID, + ListenerParams.Telecommand.TopicId, 1, 1, &EdsId); + } + else + { + Status = CFE_MISSIONLIB_INVALID_INTERFACE; + } + + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Unable to get argument type: return status = %d", Status); + break; + } + + //result = PyLong_FromLong((long int)EdsId); + result = PyTuple_Pack(2, PyLong_FromLong((long int)EdsId), PyLong_FromLong((long int) TopicId)); + } + while(0); + + Py_XDECREF(arg1); + + return result; +} + +static PyObject * CFE_MissionLib_Python_Set_PubSub(PyObject *obj, PyObject *args) +{ + PyObject *arg1; + PyObject *arg2; + PyObject *arg3; + PyObject *tempargs; + + EdsLib_Python_ObjectBase_t *Python_Packet; + EdsLib_Python_Buffer_t *StorageBuffer; + EdsLib_Binding_Buffer_Content_t edsbuf; + CCSDS_SpacePacket_t *Packet; + + CFE_SB_Listener_Component_t Params; + CFE_SB_SoftwareBus_PubSub_Interface_t PubSub; + + if (!PyArg_UnpackTuple(args, "DecodeEdsId", 3, 3, &arg1, &arg2, &arg3)) + { + PyErr_Format(PyExc_RuntimeError, "Arguments expected: InstanceNumber, TopicId, and SpacePacket Message"); + return Py_False; + } + Py_INCREF(arg1); + Py_INCREF(arg2); + Py_INCREF(arg3); + + do + { + if (PyNumber_Check(arg1)) + { + tempargs = PyNumber_Long(arg1); + Py_INCREF(tempargs); + + Params.Telecommand.InstanceNumber = PyLong_AsUnsignedLong(tempargs); + Py_DECREF(tempargs); + } + else + { + PyErr_Format(PyExc_RuntimeError, "InstanceNumber needs to be an integer"); + return Py_False; + } + + + if (PyNumber_Check(arg2)) + { + tempargs = PyNumber_Long(arg2); + Py_INCREF(tempargs); + + Params.Telecommand.TopicId = PyLong_AsUnsignedLong(tempargs); + Py_DECREF(tempargs); + } + else + { + PyErr_Format(PyExc_RuntimeError, "InstanceNumber neesd to be an integer"); + return Py_False; + } + + // Dive through an EdsLib python base object to get to the actual EDS data + Python_Packet = (EdsLib_Python_ObjectBase_t *) arg3; + StorageBuffer = Python_Packet->StorageBuf; + edsbuf = StorageBuffer->edsbuf; + Packet = (CCSDS_SpacePacket_t *) edsbuf.Data; + + CFE_SB_MapListenerComponent(&PubSub, &Params); + CFE_SB_Set_PubSub_Parameters(Packet, &PubSub); + + } while(0); + + Py_XDECREF(arg1); + Py_XDECREF(arg2); + Py_XDECREF(arg3); + + return Py_True; +} + +static PyObject * CFE_MissionLib_Python_Instance_iter(PyObject *obj) +{ + CFE_MissionLib_Python_InstanceIterator_t *InstIter; + PyObject *result = NULL; + + InstIter = PyObject_GC_New(CFE_MissionLib_Python_InstanceIterator_t, &CFE_MissionLib_Python_InstanceIteratorType); + + if (InstIter == NULL) + { + return NULL; + } + + Py_INCREF(obj); + InstIter->refobj = obj; + InstIter->Index = 1; + + result = (PyObject *)InstIter; + PyObject_GC_Track(result); + + return result; +} + +static void CFE_MissionLib_Python_InstanceIterator_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_InstanceIterator_t *self = (CFE_MissionLib_Python_InstanceIterator_t*)obj; + PyObject_GC_UnTrack(self); + Py_XDECREF(self->refobj); + PyObject_GC_Del(self); +} + +static int CFE_MissionLib_Python_InstanceIterator_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_InstanceIterator_t *self = (CFE_MissionLib_Python_InstanceIterator_t*)obj; + Py_VISIT(self->refobj); + return 0; +} + +static int CFE_MissionLib_Python_InstanceIterator_clear(PyObject *obj) +{ + CFE_MissionLib_Python_InstanceIterator_t *self = (CFE_MissionLib_Python_InstanceIterator_t*)obj; + Py_CLEAR(self->refobj); + return 0; +} + +static PyObject *CFE_MissionLib_Python_InstanceIterator_iternext(PyObject *obj) +{ + CFE_MissionLib_Python_InstanceIterator_t *self = (CFE_MissionLib_Python_InstanceIterator_t*)obj; + CFE_MissionLib_Python_Database_t *dbobj = NULL; + const char * Label = NULL; + char Buffer[64]; + char CheckBuffer[64]; + + PyObject *key = NULL; + PyObject *instanceid = NULL; + PyObject *result = NULL; + + do + { + if (self->refobj == NULL) + { + break; + } + + dbobj = (CFE_MissionLib_Python_Database_t *)self->refobj; + + Label = CFE_MissionLib_GetInstanceName(dbobj->IntfDb, self->Index, Buffer, sizeof(Buffer)); + + // If the instance doesn't exist then the return buffer is a string of the input index + snprintf(CheckBuffer, sizeof(CheckBuffer), "%u", (unsigned int) self->Index); + if (strcmp(Label,CheckBuffer) == 0) + { + break; + } + + key = PyUnicode_FromString(Label); + Py_INCREF(key); + + instanceid = PyLong_FromLong((long int)self->Index); + Py_INCREF(instanceid); + + ++self->Index; + result = PyTuple_Pack(2, key, instanceid); + } + while(0); + + Py_XDECREF(key); + Py_XDECREF(instanceid); + + return result; +} diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_interface.c b/cfecfs/missionlib/python/src/cfe_missionlib_python_interface.c new file mode 100644 index 000000000..59383ad6e --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_interface.c @@ -0,0 +1,493 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_interface.c +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey@nasa.gov +** +** Purpose: +** Implement the "interface" type +** +** This is a datatype which refers back to an interface in a CFE Interface database. +** +******************************************************************************/ + +#include "cfe_missionlib_python_internal.h" +#include +#include +#include "cfe_mission_eds_designparameters.h" +#include "cfe_mission_eds_interface_parameters.h" + + +PyObject *CFE_MissionLib_Python_InterfaceCache = NULL; + +static void CFE_MissionLib_Python_Interface_dealloc(PyObject * obj); +static PyObject * CFE_MissionLib_Python_Interface_new(PyTypeObject *obj, PyObject *args, PyObject *kwds); +static PyObject * CFE_MissionLib_Python_Interface_gettopic(PyObject *obj, PyObject *arg); +static PyObject * CFE_MissionLib_Python_Interface_repr(PyObject *obj); +static int CFE_MissionLib_Python_Interface_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_Interface_clear(PyObject *obj); +static PyObject * CFE_MissionLib_Python_Interface_GetCmdMessage(PyObject *obj, PyObject *args); + +static PyObject * CFE_MissionLib_Python_Interface_iter(PyObject *obj); +static void CFE_MissionLib_Python_InterfaceIterator_dealloc(PyObject * obj); +static int CFE_MissionLib_Python_InterfaceIterator_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_InterfaceIterator_clear(PyObject *obj); +static PyObject * CFE_MissionLib_Python_InterfaceIterator_iternext(PyObject *obj); + +static CFE_MissionLib_Python_Interface_t *CFE_MissionLib_Python_Interface_GetFromInterfaceId_Impl(PyTypeObject *obj, PyObject *dbobj, uint16_t IntfId); + +static PyMethodDef CFE_MissionLib_Python_Interface_methods[] = +{ + {"Topic", CFE_MissionLib_Python_Interface_gettopic, METH_O, "Lookup a Topic from an Interface."}, + {"GetCmdMessage", CFE_MissionLib_Python_Interface_GetCmdMessage, METH_VARARGS, "Get a CFE command message EDS Object from a Topic"}, + {NULL} /* Sentinel */ +}; + +static struct PyMemberDef CFE_MissionLib_Python_Interface_members[] = +{ + {"Name", T_OBJECT_EX, offsetof(CFE_MissionLib_Python_Interface_t, IntfName), READONLY, "Interface Name" }, + {NULL} /* Sentinel */ +}; + + +PyTypeObject CFE_MissionLib_Python_InterfaceType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("Interface"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_Interface_t), + .tp_dealloc = CFE_MissionLib_Python_Interface_dealloc, + .tp_new = CFE_MissionLib_Python_Interface_new, + .tp_methods = CFE_MissionLib_Python_Interface_methods, + .tp_members = CFE_MissionLib_Python_Interface_members, + .tp_repr = CFE_MissionLib_Python_Interface_repr, + .tp_traverse = CFE_MissionLib_Python_Interface_traverse, + .tp_clear = CFE_MissionLib_Python_Interface_clear, + .tp_iter = CFE_MissionLib_Python_Interface_iter, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, + .tp_weaklistoffset = offsetof(CFE_MissionLib_Python_Interface_t, WeakRefList), + .tp_doc = "Interface database entry" +}; + +PyTypeObject CFE_MissionLib_Python_InterfaceIteratorType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("InterfaceIterator"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_InterfaceIterator_t), + .tp_dealloc = CFE_MissionLib_Python_InterfaceIterator_dealloc, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = CFE_MissionLib_Python_InterfaceIterator_traverse, + .tp_clear = CFE_MissionLib_Python_InterfaceIterator_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = CFE_MissionLib_Python_InterfaceIterator_iternext, + .tp_doc = PyDoc_STR("CFE MissionLib InterfaceIteratorType") +}; + +static int CFE_MissionLib_Python_Interface_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_Interface_t *self = (CFE_MissionLib_Python_Interface_t *)obj; + Py_VISIT(self->DbObj); + Py_VISIT(self->IntfName); + Py_VISIT(self->TypeCache); + return 0; +} + +static int CFE_MissionLib_Python_Interface_clear(PyObject *obj) +{ + CFE_MissionLib_Python_Interface_t *self = (CFE_MissionLib_Python_Interface_t *)obj; + Py_CLEAR(self->DbObj); + Py_CLEAR(self->IntfName); + Py_CLEAR(self->TypeCache); + return 0; +} + +static void CFE_MissionLib_Python_Interface_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_Interface_t *self = (CFE_MissionLib_Python_Interface_t *)obj; + Py_CLEAR(self->DbObj); + Py_CLEAR(self->IntfName); + Py_CLEAR(self->TypeCache); + + if (self->WeakRefList != NULL) + { + PyObject_ClearWeakRefs(obj); + self->WeakRefList = NULL; + } + + CFE_MissionLib_Python_InterfaceType.tp_base->tp_dealloc(obj); +} + +static CFE_MissionLib_Python_Interface_t *CFE_MissionLib_Python_Interface_GetFromInterfaceId_Impl(PyTypeObject *obj, PyObject *dbobj, uint16_t IntfId) +{ + CFE_MissionLib_Python_Database_t *DbObj = (CFE_MissionLib_Python_Database_t *)dbobj; + CFE_MissionLib_Python_Interface_t *self; + const char *InterfaceName = NULL; + PyObject *IntfIdVal; + PyObject *weakref; + + do + { + IntfIdVal = PyLong_FromUnsignedLong(IntfId); + if (IntfIdVal == NULL) + { + break; + } + + weakref = PyDict_GetItem(CFE_MissionLib_Python_InterfaceCache, IntfIdVal); + if (weakref != NULL) + { + self = (CFE_MissionLib_Python_Interface_t *)PyWeakref_GetObject(weakref); + if (Py_TYPE(self) == &CFE_MissionLib_Python_InterfaceType) + { + if (IntfId == self->InterfaceId) + { + Py_INCREF(self); + break; + } + + /* weakref expired, needs to be recreated */ + self = NULL; + } + } + + self = (CFE_MissionLib_Python_Interface_t*)obj->tp_alloc(obj, 0); + if (self == NULL) + { + break; + } + + self->TypeCache = PyDict_New(); + if (self->TypeCache == NULL) + { + Py_DECREF(self); + break; + } + + Py_INCREF(DbObj); + self->DbObj = DbObj; + InterfaceName = CFE_MissionLib_GetInterfaceName(DbObj->IntfDb, IntfId); + self->IntfName = PyUnicode_FromFormat("%s",InterfaceName); + self->InterfaceId = IntfId; + CFE_MissionLib_GetInterfaceInfo(DbObj->IntfDb, IntfId, &self->IntfInfo); + + /* Create a weak reference to store in the local cache in case this + * database is constructed again. */ + weakref = PyWeakref_NewRef((PyObject*)self, NULL); + if (weakref == NULL) + { + Py_DECREF(self); + break; + } + + PyDict_SetItem(CFE_MissionLib_Python_InterfaceCache, IntfIdVal, weakref); + Py_DECREF(weakref); + } + while(0); + + Py_XDECREF(IntfIdVal); + return self; +} + +//const CFE_MissionLib_InterfaceId_Entry_t *CFE_MissionLib_Python_Interface_GetEntry(PyObject *obj) +//{ +// if (obj == NULL) +// { +// return NULL; +// } +// if (!PyObject_TypeCheck(obj, &CFE_MissionLib_Python_InterfaceType)) +// { +// PyErr_SetObject(PyExc_TypeError, obj); +// return NULL; +// } +// +// return ((CFE_MissionLib_Python_Interface_t*)obj)->IntfEntry; +//} + +static PyObject *CFE_MissionLib_Python_Interface_new(PyTypeObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *arg1; + PyObject *arg2; + PyObject *arg3 = NULL; + CFE_MissionLib_Python_Database_t *DbObj = NULL; + + CFE_MissionLib_InterfaceInfo_t IntfInfo; + int32_t Status; + + uint16_t InterfaceId = 0; // 0 is an invalid InterfaceId + + PyObject *tempargs = NULL; + PyObject *result = NULL; + + /* + * Interface entries are constructed from two values: + * - An Interface Database + * - An Interface Identifier + * - An EdsDb Python Object (only needed if a string is used as an Interface Database Identifier) + * + * Databases are natively of the CFE_MissionLib_Python_Database_t type + * Interface Identifiers are indices which are natively unsigned integers (uint16_t specifically) + * + * However, either value can alternatively be supplied as a string, + * in which case it can be looked up and converted to the native value. + */ + if (!PyArg_UnpackTuple(args, "Interface_new", 2, 3, &arg1, &arg2, &arg3)) + { + PyErr_Format(PyExc_RuntimeError, "Interface arguments expected: Interface Database, Interface Identifier, and EdsDb Python Object (optional)"); + return NULL; + } + + do + { + // Set up the Interface Database Python Object + if (Py_TYPE(arg1) == &CFE_MissionLib_Python_DatabaseType) + { + DbObj = (CFE_MissionLib_Python_Database_t*)arg1; + Py_INCREF(DbObj); + } + else + { + if (arg3 != NULL) + { + tempargs = PyTuple_Pack(2, arg1, arg3); + } + else + { + tempargs = PyTuple_Pack(2, arg1, Py_None); + } + + if (tempargs == NULL) + { + break; + } + DbObj = (CFE_MissionLib_Python_Database_t*)PyObject_Call((PyObject*)&CFE_MissionLib_Python_DatabaseType, tempargs, NULL); + Py_DECREF(tempargs); + tempargs = NULL; + } + + if (DbObj == NULL) + { + break; + } + + // Set up the Interface Python Object from an Interface Id or Interface Name + if (PyNumber_Check(arg2)) + { + tempargs = PyNumber_Long(arg2); + if (tempargs == NULL) + { + break; + } + + InterfaceId = PyLong_AsUnsignedLong(tempargs); + Status = CFE_MissionLib_GetInterfaceInfo(DbObj->IntfDb, InterfaceId, &IntfInfo); + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Invalid Interface Id: %u\tStatus: %d", InterfaceId, Status); + break; + } + } + else + { + if (PyUnicode_Check(arg2)) + { + tempargs = PyUnicode_AsUTF8String(arg2); + } + else + { + tempargs = PyObject_Bytes(arg2); + } + + if (tempargs == NULL) + { + break; + } + + Status = CFE_MissionLib_FindInterfaceByName(DbObj->IntfDb, PyBytes_AsString(tempargs), &InterfaceId); + + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Interface %s undefined\tStatus: %d", PyBytes_AsString(tempargs), Status); + break; + } + } + + result = (PyObject*)CFE_MissionLib_Python_Interface_GetFromInterfaceId_Impl(&CFE_MissionLib_Python_InterfaceType, (PyObject *)DbObj, InterfaceId); + } + while(0); + + /* decrement refcount for all temporary objects created */ + Py_XDECREF(DbObj); + Py_XDECREF(tempargs); + + return result; +} + +PyObject *CFE_MissionLib_Python_Interface_repr(PyObject *obj) +{ + CFE_MissionLib_Python_Interface_t *self = (CFE_MissionLib_Python_Interface_t *)obj; + return PyUnicode_FromFormat("%s(%R,%R)", obj->ob_type->tp_name, self->DbObj->DbName, self->IntfName); +} + +static PyObject *CFE_MissionLib_Python_Interface_gettopic(PyObject *obj, PyObject *arg) +{ + CFE_MissionLib_Python_Interface_t *IntfObj = (CFE_MissionLib_Python_Interface_t *)obj; + PyObject *tempargs = NULL; + PyObject *result = NULL; + + tempargs = PyTuple_Pack(3, IntfObj->DbObj, IntfObj, arg); + if (tempargs == NULL) + { + return NULL; + } + result = PyObject_Call((PyObject*)&CFE_MissionLib_Python_TopicType, tempargs, NULL); + + Py_DECREF(tempargs); + return result; +} + +PyObject *CFE_MissionLib_Python_Interface_GetFromIntfName(CFE_MissionLib_Python_Database_t *dbobj, PyObject *IntfName) +{ + int32_t status; + uint16_t InterfaceId; + + status = CFE_MissionLib_FindInterfaceByName(dbobj->IntfDb, PyBytes_AsString(IntfName), &InterfaceId); + + if (status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Interface %s undefined", PyBytes_AsString(IntfName)); + return NULL; + } + + return (PyObject*)CFE_MissionLib_Python_Interface_GetFromInterfaceId_Impl(&CFE_MissionLib_Python_InterfaceType, (PyObject *)dbobj, InterfaceId); +} + +static PyObject * CFE_MissionLib_Python_Interface_iter(PyObject *obj) +{ + CFE_MissionLib_Python_Interface_t *Intf = (CFE_MissionLib_Python_Interface_t *) obj; + CFE_MissionLib_Python_InterfaceIterator_t *IntfIter; + PyObject *result = NULL; + + if (Intf->IntfInfo.NumTopics != 0) + { + IntfIter = PyObject_GC_New(CFE_MissionLib_Python_InterfaceIterator_t, &CFE_MissionLib_Python_InterfaceIteratorType); + + if (IntfIter == NULL) + { + return NULL; + } + + IntfIter->Index = 1; + + Py_INCREF(obj); + IntfIter->refobj = obj; + + result = (PyObject *)IntfIter; + PyObject_GC_Track(result); + } + else + { + PyErr_Format(PyExc_RuntimeError, "Not an iterable interface"); + } + return result; +} + +static void CFE_MissionLib_Python_InterfaceIterator_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_InterfaceIterator_t *self = (CFE_MissionLib_Python_InterfaceIterator_t*)obj; + PyObject_GC_UnTrack(self); + Py_XDECREF(self->refobj); + PyObject_GC_Del(self); +} + +static int CFE_MissionLib_Python_InterfaceIterator_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_InterfaceIterator_t *self = (CFE_MissionLib_Python_InterfaceIterator_t*)obj; + Py_VISIT(self->refobj); + return 0; +} + +static int CFE_MissionLib_Python_InterfaceIterator_clear(PyObject *obj) +{ + CFE_MissionLib_Python_InterfaceIterator_t *self = (CFE_MissionLib_Python_InterfaceIterator_t*)obj; + Py_CLEAR(self->refobj); + return 0; +} + +static PyObject *CFE_MissionLib_Python_InterfaceIterator_iternext(PyObject *obj) +{ + CFE_MissionLib_Python_InterfaceIterator_t *self = (CFE_MissionLib_Python_InterfaceIterator_t*)obj; + CFE_MissionLib_Python_Interface_t *intf = NULL; + const char * Label = NULL; + uint16_t idx; + PyObject *key = NULL; + PyObject *topicid = NULL; + PyObject *result = NULL; + + do + { + if (self->refobj == NULL) + { + break; + } + + intf = (CFE_MissionLib_Python_Interface_t *)self->refobj; + idx = self->Index; + + do + { + Label = CFE_MissionLib_GetTopicName(intf->DbObj->IntfDb, intf->InterfaceId, idx); + ++idx; + } + while((Label == NULL) && (idx <= intf->IntfInfo.NumTopics+1)); + + if (idx <= intf->IntfInfo.NumTopics+1) + { + key = PyUnicode_FromString(Label); + + if (key == NULL) + { + /* end */ + Py_CLEAR(self->refobj); + break; + } + Py_INCREF(key); + + topicid = PyLong_FromLong(idx-1); + Py_INCREF(topicid); + + self->Index = idx; + result = PyTuple_Pack(2, key, topicid); + } + } + while(0); + + Py_XDECREF(key); + Py_XDECREF(topicid); + + return result; +} + +static PyObject *CFE_MissionLib_Python_Interface_GetCmdMessage(PyObject *obj, PyObject *args) +{ + return PyUnicode_FromFormat("Interface_GetCmdMessage still needs to be implemented"); +} diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_internal.h b/cfecfs/missionlib/python/src/cfe_missionlib_python_internal.h new file mode 100644 index 000000000..b5cece9bd --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_internal.h @@ -0,0 +1,136 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_internal.h +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey +** +** Purpose: +** Internal Header file for Python / cfe_missionlib bindings +** +******************************************************************************/ + + +#ifndef _CFE_MISSIONLIB_PYTHON_INTERNAL_H_ +#define _CFE_MISSIONLIB_PYTHON_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cfe_missionlib_python.h" +#include "cfe_missionlib_database_types.h" +#include "edslib_binding_objects.h" +#include "edslib_python_internal.h" + +typedef struct +{ + PyObject_HEAD + void *dl; + const CFE_MissionLib_SoftwareBus_Interface_t *IntfDb; + EdsLib_Python_Database_t *EdsDbObj; + PyObject *DbName; + /* TypeCache contains weak references to Databases, such that they + * will not be re-created each time they are required. This also gives persistence, + * i.e. repeated calls to lookup the same type give the same object, instead of a + * separate-but-equal object. */ + PyObject *TypeCache; + PyObject *WeakRefList; +} CFE_MissionLib_Python_Database_t; + +typedef struct +{ + PyObject_HEAD + CFE_MissionLib_Python_Database_t *DbObj; + PyObject *IntfName; + uint16_t InterfaceId; + CFE_MissionLib_InterfaceInfo_t IntfInfo; + + /* TypeCache contains weak references to Interfaces in the db, such that they + * will not be re-created each time they are required. This also gives persistence, + * i.e. repeated calls to lookup the same type give the same object, instead of a + * separate-but-equal object. */ + PyObject *TypeCache; + PyObject *WeakRefList; +} CFE_MissionLib_Python_Interface_t; + +typedef struct +{ + PyObject_HEAD + CFE_MissionLib_Python_Interface_t *IntfObj; + PyObject *TopicName; + uint16_t TopicId; + EdsLib_Id_t EdsId; + CFE_MissionLib_IndicationInfo_t IndInfo; + + /* TypeCache contains weak references to Topics in the db, such that they + * will not be re-created each time they are required. This also gives persistence, + * i.e. repeated calls to lookup the same type give the same object, instead of a + * separate-but-equal object. */ + PyObject *TypeCache; + PyObject *WeakRefList; +} CFE_MissionLib_Python_Topic_t; + +typedef struct +{ + PyObject_HEAD + uint16_t Index; + PyObject* refobj; +} CFE_MissionLib_Python_InstanceIterator_t; + +typedef struct +{ + PyObject_HEAD + uint16_t Index; + PyObject* refobj; +} CFE_MissionLib_Python_InterfaceIterator_t; + +typedef struct +{ + PyObject_HEAD + EdsLib_Id_t Index; + PyObject* refobj; +} CFE_MissionLib_Python_TopicIterator_t; + +const CFE_MissionLib_SoftwareBus_Interface_t *CFE_MissionLib_Python_Database_GetDB(PyObject *obj); +//const CFE_MissionLib_InterfaceId_Entry_t *CFE_MissionLib_Python_Interface_GetEntry(PyObject *obj); +//const CFE_MissionLib_TopicId_Entry_t *CFE_MissionLib_Python_Topic_GetEntry(PyObject *obj); + +PyObject *CFE_MissionLib_Python_Interface_GetFromIntfName(CFE_MissionLib_Python_Database_t *obj, PyObject *InterfaceName); +PyObject *CFE_MissionLib_Python_Topic_GetFromTopicName(CFE_MissionLib_Python_Interface_t *obj, PyObject *TopicName); + +PyObject *CFE_MissionLib_Python_Database_CreateFromStaticDB(const char *Name, const CFE_MissionLib_SoftwareBus_Interface_t *IntfDb); + +extern PyObject *CFE_MissionLib_Python_DatabaseCache; +extern PyObject *CFE_MissionLib_Python_InterfaceCache; +extern PyObject *CFE_MissionLib_Python_TopicCache; + +extern PyTypeObject CFE_MissionLib_Python_DatabaseType; +extern PyTypeObject CFE_MissionLib_Python_InterfaceType; +extern PyTypeObject CFE_MissionLib_Python_TopicType; + +#endif /* _CFE_MISSIONLIB_PYTHON_INTERNAL_H_ */ diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_module.c b/cfecfs/missionlib/python/src/cfe_missionlib_python_module.c new file mode 100644 index 000000000..7e6bdae06 --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_module.c @@ -0,0 +1,97 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_module.c +** +** Created on: Feb 11, 2017 +** Author: mathew.j.mccaskey +** +** Purpose: +** A module that implements Python bindings for CFE_MissionLib objects +** +** This file is just a thin wrapper that contains a Python-compatible init +** function for the module. This is expected to be used when the MissionLib +** module is imported directly in a Python program. No built-in database +** object will be added. The user is on their own to load a database +** separately from the CFE_MissionLib module. +** +** Note that other applications which embed Python would typically +** _not_ use this entry point. Instead, the MissionLib would be registered +** as a built-in module using its own customized init, and typically +** would add an actual database object to the module during that process. +** +******************************************************************************/ + +#include "cfe_missionlib_python.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Init Function / Entry point + * + * A wrapper function is necessary to handle the + * difference in module init between Python 2 (void) and + * Python 3 (PyObject*). The symbol also has a different + * naming convention. The Python2 wrapper just calls the + * common init function and discards the return value. + * + * This entry point also should be declared using the + * PyMODINIT_FUNC macro to ensure it is externally visible + * and has the right calling convention. + */ +#if (PY_MAJOR_VERSION >= 3) + +/* + * Python 3+ init function variant: + * Returns the new module object to the interpreter. + * Python3 should always define PyMODINIT_FUNC + */ +PyMODINIT_FUNC PyInit_CFE_MissionLib(void) +{ + /* python3 init should return the module object */ + return CFE_MissionLib_Python_CreateModule(); +} + +#else + +/* + * In case the Python headers didn't supply PyMODINIT_FUNC, + * (which is possible in older versions) then just use void. + */ +#ifndef PyMODINIT_FUNC +#define PyMODINIT_FUNC void +#endif + + +PyMODINIT_FUNC initCFE_MissionLib(void) +{ + /* python2 does not want module object */ + CFE_MissionLib_Python_CreateModule(); +} + +#endif + diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_setup.c b/cfecfs/missionlib/python/src/cfe_missionlib_python_setup.c new file mode 100644 index 000000000..050dccf9c --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_setup.c @@ -0,0 +1,127 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_setup.c +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey +** +** Purpose: +** Implement setup function / initializer for EDS/MissionLib module objects +** +******************************************************************************/ + +#include "cfe_missionlib_python_internal.h" + +/* + * Instantiation function. + * + * The C API differs between Python 3+ (PyModule_Create) and + * Python 2 (Py_InitModule3). This just creates a wrapper + * to call the right one. + */ +#if (PY_MAJOR_VERSION >= 3) + +static PyModuleDef CFE_MissionLib_Python_ModuleDef = +{ + PyModuleDef_HEAD_INIT, + CFE_MISSIONLIB_PYTHON_MODULE_NAME, + PyDoc_STR(CFE_MISSIONLIB_PYTHON_DOC), + -1 +}; + +static inline PyObject* CFE_MissionLib_Python_InstantiateModule(void) +{ + /* python3 uses PyModule_Create() API */ + return PyModule_Create(&CFE_MissionLib_Python_ModuleDef); +} + +#else + +static inline PyObject* CFE_MissionLib_Python_InstantiateModule(void) +{ + /* python2 uses Py_InitModule3() API */ + return Py_InitModule3(CFE_MISSIONLIB_PYTHON_MODULE_NAME, NULL, CFE_MISSIONLIB_PYTHON_DOC); +} + +#endif + + +PyObject* CFE_MissionLib_Python_CreateModule(void) +{ + PyObject *m = NULL; + + do + { + /* + * Prepare all of the types defined here + */ + if (PyType_Ready(&CFE_MissionLib_Python_DatabaseType) != 0 || + PyType_Ready(&CFE_MissionLib_Python_InterfaceType) != 0 || + PyType_Ready(&CFE_MissionLib_Python_TopicType) != 0) + { + break; + } + + if (CFE_MissionLib_Python_DatabaseCache == NULL) + { + CFE_MissionLib_Python_DatabaseCache = PyDict_New(); + if (CFE_MissionLib_Python_DatabaseCache == NULL) + { + break; + } + } + + if (CFE_MissionLib_Python_InterfaceCache == NULL) + { + CFE_MissionLib_Python_InterfaceCache = PyDict_New(); + if (CFE_MissionLib_Python_InterfaceCache == NULL) + { + break; + } + } + + if (CFE_MissionLib_Python_TopicCache == NULL) + { + CFE_MissionLib_Python_TopicCache = PyDict_New(); + if (CFE_MissionLib_Python_TopicCache == NULL) + { + break; + } + } + + m = CFE_MissionLib_Python_InstantiateModule(); + if (m == NULL) + { + break; + } + + /* + * Add appropriate types so object instances can be constructed + */ + PyModule_AddObject(m, "Database", (PyObject*)&CFE_MissionLib_Python_DatabaseType); + PyModule_AddObject(m, "Interface", (PyObject*)&CFE_MissionLib_Python_InterfaceType); + PyModule_AddObject(m, "Topic", (PyObject*)&CFE_MissionLib_Python_TopicType); + } + while(0); + + return m; +} diff --git a/cfecfs/missionlib/python/src/cfe_missionlib_python_topic.c b/cfecfs/missionlib/python/src/cfe_missionlib_python_topic.c new file mode 100644 index 000000000..c75eac3a0 --- /dev/null +++ b/cfecfs/missionlib/python/src/cfe_missionlib_python_topic.c @@ -0,0 +1,588 @@ +/* + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library + * + * Copyright (c) 2020 United States Government as represented by + * the Administrator of the National Aeronautics and Space Administration. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/****************************************************************************** +** File: cfe_missionlib_python_interfaceentry.c +** +** Created on: Feb 11, 2020 +** Author: mathew.j.mccaskey +** +** Purpose: +** Implement the "interface entry" type +** +** This is a datatype which refers back to an entry in a CFE Interface database. +** All instances of EDS objects should be types of this type. (note that in +** python types are also objects which have a type, recursively) +** +******************************************************************************/ + +#include "cfe_missionlib_python_internal.h" +#include +#include +#include "edslib_id.h" + +PyObject *CFE_MissionLib_Python_TopicCache = NULL; + +static void CFE_MissionLib_Python_Topic_dealloc(PyObject * obj); +static PyObject * CFE_MissionLib_Python_Topic_new(PyTypeObject *obj, PyObject *args, PyObject *kwds); +static PyObject * CFE_MissionLib_Python_Topic_repr(PyObject *obj); +static int CFE_MissionLib_Python_Topic_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_Topic_clear(PyObject *obj); +static PyObject * CFE_MissionLib_Python_Topic_GetCmdEdsIdFromCode(PyObject *obj, PyObject *args); + +static CFE_MissionLib_Python_Topic_t *CFE_MissionLib_Python_Topic_GetFromTopicId_Impl(PyTypeObject *obj, PyObject *intfobj, uint16_t TopicId); + +static PyObject * CFE_MissionLib_Python_Topic_iter(PyObject *obj); +static void CFE_MissionLib_Python_TopicIterator_dealloc(PyObject * obj); +static int CFE_MissionLib_Python_TopicIterator_traverse(PyObject *obj, visitproc visit, void *arg); +static int CFE_MissionLib_Python_TopicIterator_clear(PyObject *obj); +static PyObject * CFE_MissionLib_Python_TopicIterator_iternext(PyObject *obj); + +struct CbArg +{ + uint8_t CommandCode; + EdsLib_Id_t PossibleId; + EdsLib_Id_t EdsId; +}; + +typedef struct CbArg CbArg_t; + +void SubcommandCallback(const EdsLib_DatabaseObject_t *GD, const EdsLib_DataTypeDB_EntityInfo_t *MemberInfo, + EdsLib_GenericValueBuffer_t *ConstraintValue, void *Arg); + +static PyMethodDef CFE_MissionLib_Python_Topic_methods[] = +{ + {"GetCmdEdsId", CFE_MissionLib_Python_Topic_GetCmdEdsIdFromCode, METH_VARARGS, "Get a CFE command message EDS Object from a Topic"}, + {NULL} /* Sentinel */ +}; + +static struct PyMemberDef CFE_MissionLib_Python_Topic_members[] = +{ + {"Name", T_OBJECT_EX, offsetof(CFE_MissionLib_Python_Topic_t, TopicName), READONLY, "Topic Name" }, + {"TopicId", T_SHORT, offsetof(CFE_MissionLib_Python_Topic_t, TopicId), READONLY, "Topic ID" }, + {"EdsId", T_INT, offsetof(CFE_MissionLib_Python_Topic_t, EdsId), READONLY, "EDS ID" }, + {NULL} /* Sentinel */ +}; + + +PyTypeObject CFE_MissionLib_Python_TopicType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("Topic"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_Topic_t), + .tp_dealloc = CFE_MissionLib_Python_Topic_dealloc, + .tp_new = CFE_MissionLib_Python_Topic_new, + .tp_methods = CFE_MissionLib_Python_Topic_methods, + .tp_members = CFE_MissionLib_Python_Topic_members, + .tp_repr = CFE_MissionLib_Python_Topic_repr, + .tp_traverse = CFE_MissionLib_Python_Topic_traverse, + .tp_clear = CFE_MissionLib_Python_Topic_clear, + .tp_iter = CFE_MissionLib_Python_Topic_iter, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, + .tp_weaklistoffset = offsetof(CFE_MissionLib_Python_Topic_t, WeakRefList), + .tp_doc = "Topic entry" +}; + +PyTypeObject CFE_MissionLib_Python_TopicIteratorType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = CFE_MISSIONLIB_PYTHON_ENTITY_NAME("TopicIterator"), + .tp_basicsize = sizeof(CFE_MissionLib_Python_TopicIterator_t), + .tp_dealloc = CFE_MissionLib_Python_TopicIterator_dealloc, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = CFE_MissionLib_Python_TopicIterator_traverse, + .tp_clear = CFE_MissionLib_Python_TopicIterator_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = CFE_MissionLib_Python_TopicIterator_iternext, + .tp_doc = PyDoc_STR("CFE MissionLib TopicIteratorType") +}; + +static int CFE_MissionLib_Python_Topic_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_Topic_t *self = (CFE_MissionLib_Python_Topic_t *)obj; + Py_VISIT(self->IntfObj); + Py_VISIT(self->TopicName); + Py_VISIT(self->TypeCache); + + return 0; +} + +static int CFE_MissionLib_Python_Topic_clear(PyObject *obj) +{ + CFE_MissionLib_Python_Topic_t *self = (CFE_MissionLib_Python_Topic_t *)obj; + Py_CLEAR(self->IntfObj); + Py_CLEAR(self->TopicName); + Py_CLEAR(self->TypeCache); + return 0; +} + +static void CFE_MissionLib_Python_Topic_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_Topic_t *self = (CFE_MissionLib_Python_Topic_t *)obj; + Py_CLEAR(self->IntfObj); + Py_CLEAR(self->TypeCache); + Py_CLEAR(self->TopicName); + + if (self->WeakRefList != NULL) + { + PyObject_ClearWeakRefs(obj); + self->WeakRefList = NULL; + } + + CFE_MissionLib_Python_TopicType.tp_base->tp_dealloc(obj); +} + +static CFE_MissionLib_Python_Topic_t *CFE_MissionLib_Python_Topic_GetFromTopicId_Impl(PyTypeObject *obj, PyObject *intfobj, uint16_t TopicId) +{ + CFE_MissionLib_Python_Interface_t *IntfObj = (CFE_MissionLib_Python_Interface_t *)intfobj; + CFE_MissionLib_Python_Topic_t *self; + PyObject *TopicIdVal; + PyObject *weakref; + const char* TopicName = NULL; + + do + { + TopicIdVal = PyLong_FromUnsignedLong(TopicId); + if (TopicIdVal == NULL) + { + break; + } + + weakref = PyDict_GetItem(CFE_MissionLib_Python_TopicCache, TopicIdVal); + if (weakref != NULL) + { + self = (CFE_MissionLib_Python_Topic_t *)PyWeakref_GetObject(weakref); + if (Py_TYPE(self) == &CFE_MissionLib_Python_TopicType) + { + if (TopicId == self->TopicId) + { + Py_INCREF(self); + break; + } + + /* weakref expired, needs to be recreated */ + self = NULL; + } + } + + self = (CFE_MissionLib_Python_Topic_t*)obj->tp_alloc(obj, 0); + if (self == NULL) + { + break; + } + + self->TypeCache = PyDict_New(); + if (self->TypeCache == NULL) + { + Py_DECREF(self); + break; + } + + Py_INCREF(IntfObj); + self->IntfObj = IntfObj; + TopicName = CFE_MissionLib_GetTopicName(IntfObj->DbObj->IntfDb, IntfObj->InterfaceId, TopicId); + self->TopicName = PyUnicode_FromFormat("%s", TopicName); + self->TopicId = TopicId; + CFE_MissionLib_GetArgumentType(IntfObj->DbObj->IntfDb, IntfObj->InterfaceId, TopicId, 1, 1, &self->EdsId); + CFE_MissionLib_GetIndicationInfo(IntfObj->DbObj->IntfDb, IntfObj->InterfaceId, TopicId, 1, &self->IndInfo); + + /* Create a weak reference to store in the local cache in case this + * database is constructed again. */ + weakref = PyWeakref_NewRef((PyObject*)self, NULL); + if (weakref == NULL) + { + Py_DECREF(self); + break; + } + + PyDict_SetItem(CFE_MissionLib_Python_TopicCache, TopicIdVal, weakref); + Py_DECREF(weakref); + + } + while(0); + + Py_XDECREF(TopicIdVal); + return self; +} + +//const CFE_MissionLib_TopicId_Entry_t *CFE_MissionLib_Python_Topic_GetEntry(PyObject *obj) +//{ +// if (obj == NULL) +// { +// return NULL; +// } +// if (!PyObject_TypeCheck(obj, &CFE_MissionLib_Python_TopicType)) +// { +// PyErr_SetObject(PyExc_TypeError, obj); +// return NULL; +// } +// +// return ((CFE_MissionLib_Python_Topic_t*)obj)->TopicEntry; +//} + +static PyObject *CFE_MissionLib_Python_Topic_new(PyTypeObject *obj, PyObject *args, PyObject *kwds) +{ + PyObject *arg1; + PyObject *arg2; + PyObject *arg3; + PyObject *arg4 = NULL; + CFE_MissionLib_Python_Database_t *DbObj; + CFE_MissionLib_Python_Interface_t *IntfObj; + uint16_t TopicId = 0; // 0 is an invalid TopicId + + CFE_MissionLib_TopicInfo_t TopicInfo; + int32_t Status; + + uint16_t status; + PyObject *tempargs = NULL; + PyObject *result = NULL; + + /* + * Topic entries are constructed from three values: + * - An Interface Database + * - An Interface Identifier + * - A Topic Identifier + * - An EdsDb Python Object (only needed if Database associated with the Interface Database Identifier has not been set up) + * + * Databases are natively of the CFE_MissionLib_Python_Database_t type + * Interface Identifiers are indices which are natively unsigned integers (uint16_t specifically) + * Topic Identifiers are also indices which are natively unsigned integers (uint16_t specifically) + * + * However, any value can alternatively be supplied as a string, + * in which case it can be looked up and converted to the native value. + */ + if (!PyArg_UnpackTuple(args, "Topic_new", 3, 4, &arg1, &arg2, &arg3, &arg4)) + { + PyErr_Format(PyExc_RuntimeError, "Topic arguments expected: Interface Database, Interface Identifier, Topic Identifier, and EdsDb Python Object (optional)"); + return NULL; + } + + do + { + // Set up the Interface Database + if (Py_TYPE(arg1) == &CFE_MissionLib_Python_DatabaseType) + { + DbObj = (CFE_MissionLib_Python_Database_t*)arg1; + Py_INCREF(DbObj); + } + else + { + if (arg4 != NULL) + { + tempargs = PyTuple_Pack(2, arg1, arg4); + } + else + { + tempargs = PyTuple_Pack(2, arg1, Py_None); + } + if (tempargs == NULL) + { + break; + } + DbObj = (CFE_MissionLib_Python_Database_t*)PyObject_Call((PyObject*)&CFE_MissionLib_Python_DatabaseType, tempargs, NULL); + Py_DECREF(tempargs); + tempargs = NULL; + } + + if (DbObj == NULL) + { + break; + } + + // Set up the interface + if (Py_TYPE(arg2) == &CFE_MissionLib_Python_InterfaceType) + { + IntfObj = (CFE_MissionLib_Python_Interface_t*)arg2; + Py_INCREF(IntfObj); + } + else + { + tempargs = PyTuple_Pack(2, (PyObject *)DbObj, arg2); + if (tempargs == NULL) + { + break; + } + IntfObj = (CFE_MissionLib_Python_Interface_t*)PyObject_Call((PyObject*)&CFE_MissionLib_Python_InterfaceType, tempargs, NULL); + Py_DECREF(tempargs); + tempargs = NULL; + } + + if (IntfObj == NULL) + { + break; + } + + // Set up the topic with either a TopicId or Topic Name + if (PyNumber_Check(arg3)) + { + tempargs = PyNumber_Long(arg3); + if (tempargs == NULL) + { + break; + } + + TopicId = PyLong_AsUnsignedLong(tempargs); + Status = CFE_MissionLib_GetTopicInfo(DbObj->IntfDb, IntfObj->InterfaceId, TopicId, &TopicInfo); + if (Status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Invalid Topic Id: %u\tStatus: %d", TopicId, Status); + break; + } + } + else + { + if (PyUnicode_Check(arg3)) + { + tempargs = PyUnicode_AsUTF8String(arg3); + } + else + { + tempargs = PyObject_Bytes(arg3); + } + + if (tempargs == NULL) + { + break; + } + + status = CFE_MissionLib_FindTopicByName(DbObj->IntfDb, IntfObj->InterfaceId, PyBytes_AsString(tempargs), &TopicId); + + if (status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Topic %s undefined", PyBytes_AsString(tempargs)); + break; + } + } + + result = (PyObject*)CFE_MissionLib_Python_Topic_GetFromTopicId_Impl(&CFE_MissionLib_Python_TopicType, (PyObject *)IntfObj, TopicId); + } + while(0); + + /* decrement refcount for all temporary objects created */ + Py_XDECREF(tempargs); + + return result; +} + +PyObject *CFE_MissionLib_Python_Topic_repr(PyObject *obj) +{ + CFE_MissionLib_Python_Topic_t *self = (CFE_MissionLib_Python_Topic_t *)obj; + return PyUnicode_FromFormat("%s(%R,%R,%R)", obj->ob_type->tp_name, self->IntfObj->DbObj->DbName, self->IntfObj->IntfName, self->TopicName); +} + +PyObject *CFE_MissionLib_Python_Topic_GetFromTopicName(CFE_MissionLib_Python_Interface_t *intfobj, PyObject *TopicName) +{ + CFE_MissionLib_Python_Topic_t *TopicEntry; + + PyObject *weakref; + int32_t status; + uint16_t TopicId; + + weakref = PyDict_GetItem(CFE_MissionLib_Python_TopicCache, TopicName); + if (weakref != NULL) + { + TopicEntry = (CFE_MissionLib_Python_Topic_t *)PyWeakref_GetObject(weakref); + if (Py_TYPE(TopicEntry) == &CFE_MissionLib_Python_TopicType) + { + if (TopicEntry->TopicName != TopicName) + { + PyErr_SetString(PyExc_RuntimeError, "Topic name conflict"); + TopicEntry = NULL; + } + else + { + Py_INCREF(TopicEntry); + } + return (PyObject*)TopicEntry; + } + } + + status = CFE_MissionLib_FindTopicByName(intfobj->DbObj->IntfDb, intfobj->InterfaceId, PyBytes_AsString(TopicName), &TopicId); + + if (status != CFE_MISSIONLIB_SUCCESS) + { + PyErr_Format(PyExc_RuntimeError, "Topic %s undefined:%u", PyBytes_AsString(TopicName)); + return NULL; + } + + return (PyObject*)CFE_MissionLib_Python_Topic_GetFromTopicId_Impl(&CFE_MissionLib_Python_TopicType, (PyObject *)intfobj, TopicId); +} + +static PyObject * CFE_MissionLib_Python_Topic_iter(PyObject *obj) +{ + CFE_MissionLib_Python_Topic_t *Topic = (CFE_MissionLib_Python_Topic_t *) obj; + CFE_MissionLib_Python_TopicIterator_t *TopicIter; + PyObject *result = NULL; + + if (Topic->IndInfo.NumSubcommands > 0) + { + TopicIter = PyObject_GC_New(CFE_MissionLib_Python_TopicIterator_t, &CFE_MissionLib_Python_TopicIteratorType); + + if (TopicIter == NULL) + { + return NULL; + } + + Py_INCREF(obj); + TopicIter->Index = 0; + TopicIter->refobj = obj; + + result = (PyObject *)TopicIter; + PyObject_GC_Track(result); + } + else + { + PyErr_Format(PyExc_RuntimeError, "Not an iterable Topic"); + } + return result; +} + +static void CFE_MissionLib_Python_TopicIterator_dealloc(PyObject * obj) +{ + CFE_MissionLib_Python_TopicIterator_t *self = (CFE_MissionLib_Python_TopicIterator_t*)obj; + PyObject_GC_UnTrack(self); + Py_XDECREF(self->refobj); + PyObject_GC_Del(self); +} + +static int CFE_MissionLib_Python_TopicIterator_traverse(PyObject *obj, visitproc visit, void *arg) +{ + CFE_MissionLib_Python_TopicIterator_t *self = (CFE_MissionLib_Python_TopicIterator_t*)obj; + Py_VISIT(self->refobj); + return 0; +} + +static int CFE_MissionLib_Python_TopicIterator_clear(PyObject *obj) +{ + CFE_MissionLib_Python_TopicIterator_t *self = (CFE_MissionLib_Python_TopicIterator_t*)obj; + Py_CLEAR(self->refobj); + return 0; +} + +static PyObject *CFE_MissionLib_Python_TopicIterator_iternext(PyObject *obj) +{ + CFE_MissionLib_Python_TopicIterator_t *self = (CFE_MissionLib_Python_TopicIterator_t*)obj; + CFE_MissionLib_Python_Topic_t *topic = NULL; + EdsLib_Python_Database_t *EdsDb; + uint16_t idx; + EdsLib_Id_t PossibleId; + + PyObject *key = NULL; + PyObject *edsid = NULL; + PyObject *result = NULL; + + do + { + if (self->refobj == NULL) + { + break; + } + + topic = (CFE_MissionLib_Python_Topic_t *)self->refobj; + EdsDb = topic->IntfObj->DbObj->EdsDbObj; + idx = self->Index; + + if (EdsLib_DataTypeDB_GetDerivedTypeById(EdsDb->GD, topic->EdsId, idx, &PossibleId) == EDSLIB_SUCCESS) + { + + key = PyUnicode_FromString(EdsLib_DisplayDB_GetBaseName(EdsDb->GD, PossibleId)); + Py_INCREF(key); + + edsid = PyLong_FromLong((long int)PossibleId); + Py_INCREF(edsid); + + if((key == NULL) || (edsid == NULL)) + { + Py_CLEAR(self->refobj); + break; + } + + ++self->Index; + result = PyTuple_Pack(2, key, edsid); + } + } + while(0); + + Py_XDECREF(key); + Py_XDECREF(edsid); + + return result; +} + +void SubcommandCallback(const EdsLib_DatabaseObject_t *GD, const EdsLib_DataTypeDB_EntityInfo_t *MemberInfo, + EdsLib_GenericValueBuffer_t *ConstraintValue, void *Arg) +{ + CbArg_t *Argument = (CbArg_t *) Arg; + + if (ConstraintValue->Value.u8 == Argument->CommandCode ) + { + Argument->EdsId = Argument->PossibleId; + } +} + +static PyObject *CFE_MissionLib_Python_Topic_GetCmdEdsIdFromCode(PyObject *obj, PyObject *args) +{ + CFE_MissionLib_Python_Topic_t *self = (CFE_MissionLib_Python_Topic_t*) obj; + EdsLib_Python_Database_t *EdsDb; + PyObject *arg1; + PyObject *tempargs; + uint8_t CommandCode; + int32_t idx; + EdsLib_Id_t PossibleId; + EdsLib_ConstraintCallback_t Callback = SubcommandCallback; + CbArg_t Argument; + PyObject *result; + + do + { + if (!PyArg_UnpackTuple(args, "CommandCode", 1, 1, &arg1)) + { + PyErr_Format(PyExc_RuntimeError, "Topic arguments expected: Command Code"); + return NULL; + } + + // Get the command code from the input argument + if (PyNumber_Check(arg1)) + { + tempargs = PyNumber_Long(arg1); + if (tempargs == NULL) + { + break; + } + + CommandCode = PyLong_AsUnsignedLong(tempargs); + } + + EdsDb = self->IntfObj->DbObj->EdsDbObj; + Argument.CommandCode = CommandCode; + + for (idx = 0; idx < self->IndInfo.NumSubcommands; idx++) + { + EdsLib_DataTypeDB_GetDerivedTypeById(EdsDb->GD, self->EdsId, idx, &PossibleId); + Argument.PossibleId = PossibleId; + EdsLib_DataTypeDB_ConstraintIterator(EdsDb->GD, self->EdsId, PossibleId, Callback, (void *)&Argument); + } + } while(0); + + Py_XDECREF(tempargs); + result = PyLong_FromLong((long int) Argument.EdsId); + return result; +} diff --git a/cfecfs/util/cmdUtil.c b/cfecfs/util/cmdUtil.c index 801865ba9..14c893553 100644 --- a/cfecfs/util/cmdUtil.c +++ b/cfecfs/util/cmdUtil.c @@ -300,6 +300,7 @@ static void Enumerate_Constraint_Callback(const EdsLib_DatabaseObject_t *GD, con case EDSLIB_BASICTYPE_BINARY: strncpy(Buffer, ConstraintValue->Value.StringData, CONSTRAINT_BUFSIZE-1); Buffer[CONSTRAINT_BUFSIZE-1] = 0; + break; default: break; } @@ -581,12 +582,6 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - if (EdsLib_DataTypeDB_GetTypeInfo(&EDS_DATABASE, CommandData.ActualArg, &CommandData.EdsTypeInfo) != EDSLIB_SUCCESS) - { - fprintf(stderr,"Error retrieving info for code %x\n", (unsigned int)CommandData.ActualArg); - return EXIT_FAILURE; - } - if (EdsLib_DisplayDB_GetIndexByName(&EDS_DATABASE, CommandData.ActualArg, "Payload", &Idx) == EDSLIB_SUCCESS) { EdsLib_DataTypeDB_GetMemberByIndex(&EDS_DATABASE, CommandData.ActualArg, Idx, &CommandData.EdsPayloadInfo); diff --git a/edslib/fsw/inc/edslib_displaydb.h b/edslib/fsw/inc/edslib_displaydb.h index 6b589ffb8..732a47453 100644 --- a/edslib/fsw/inc/edslib_displaydb.h +++ b/edslib/fsw/inc/edslib_displaydb.h @@ -1,5 +1,6 @@ /* * LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library * * Copyright (c) 2020 United States Government as represented by * the Administrator of the National Aeronautics and Space Administration. @@ -337,6 +338,9 @@ const char *EdsLib_DisplayDB_GetEnumLabel(const EdsLib_DatabaseObject_t *GD, Eds void EdsLib_DisplayDB_GetEnumValue(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, const char *String, EdsLib_GenericValueBuffer_t *ValueBuffer); void EdsLib_DisplayDB_IterateEnumValues(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, EdsLib_SymbolCallback_t Callback, void *Arg); +intmax_t EdsLib_DisplayDB_GetEnumValueByIndex(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, uint16_t Index); +const char *EdsLib_DisplayDB_GetEnumLabelByIndex(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, uint16_t Index, char *Buffer, uint32_t BufferSize); + /** * Base64 encoding helper function * diff --git a/edslib/fsw/src/edslib_displaydb_api.c b/edslib/fsw/src/edslib_displaydb_api.c index c76244746..bc8254f82 100644 --- a/edslib/fsw/src/edslib_displaydb_api.c +++ b/edslib/fsw/src/edslib_displaydb_api.c @@ -1,5 +1,6 @@ /* * LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library * * Copyright (c) 2020 United States Government as represented by * the Administrator of the National Aeronautics and Space Administration. @@ -496,7 +497,6 @@ const char *EdsLib_DisplayDB_GetEnumLabel(const EdsLib_DatabaseObject_t *GD, Eds } } - if (TableEnt == NULL) { return NULL; @@ -569,3 +569,64 @@ void EdsLib_DisplayDB_IterateEnumValues(const EdsLib_DatabaseObject_t *GD, EdsLi --SymbolCount; } } + +const char * EdsLib_DisplayDB_GetEnumLabelByIndex(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, uint16_t Index, char *Buffer, uint32_t BufferSize) +{ + EdsLib_DatabaseRef_t TempRef; + const EdsLib_DisplayDB_Entry_t *DisplayInfoPtr; + const EdsLib_SymbolTableEntry_t *Symbol = NULL; + uint16_t SymbolCount; + const char * Result; + + EdsLib_Decode_StructId(&TempRef, EdsId); + DisplayInfoPtr = EdsLib_DisplayDB_GetEntry(GD, &TempRef); + + if (DisplayInfoPtr != NULL && DisplayInfoPtr->DisplayHint == EDSLIB_DISPLAYHINT_ENUM_SYMTABLE) + { + Symbol = DisplayInfoPtr->DisplayArg.SymTable; + SymbolCount = DisplayInfoPtr->DisplayArgTableSize; + } + + if (Symbol != NULL && Index < SymbolCount) + { + Symbol = Symbol + Index; + snprintf(Buffer, BufferSize,"%s", Symbol->SymName); + Result = Buffer; + } + else + { + Result = UNDEF_STRING; + } + + return Result; +} + +intmax_t EdsLib_DisplayDB_GetEnumValueByIndex(const EdsLib_DatabaseObject_t *GD, EdsLib_Id_t EdsId, uint16_t Index) +{ + EdsLib_DatabaseRef_t TempRef; + const EdsLib_DisplayDB_Entry_t *DisplayInfoPtr; + const EdsLib_SymbolTableEntry_t *Symbol = NULL; + uint16_t SymbolCount; + intmax_t Result; + + EdsLib_Decode_StructId(&TempRef, EdsId); + DisplayInfoPtr = EdsLib_DisplayDB_GetEntry(GD, &TempRef); + + if (DisplayInfoPtr != NULL && DisplayInfoPtr->DisplayHint == EDSLIB_DISPLAYHINT_ENUM_SYMTABLE) + { + Symbol = DisplayInfoPtr->DisplayArg.SymTable; + SymbolCount = DisplayInfoPtr->DisplayArgTableSize; + } + + if (Symbol != NULL && Index < SymbolCount) + { + Symbol = Symbol + Index; + Result = Symbol->SymValue; + } + else + { + Result = (intmax_t) EDSLIB_FAILURE; + } + + return Result; +} diff --git a/edslib/python/src/edslib_python_base.c b/edslib/python/src/edslib_python_base.c index 6ff8eeac6..e463bf075 100644 --- a/edslib/python/src/edslib_python_base.c +++ b/edslib/python/src/edslib_python_base.c @@ -134,7 +134,7 @@ PyTypeObject EdsLib_Python_ObjectBaseType = .tp_call = EdsLib_Python_ObjectBase_call, .tp_as_buffer = &EdsLib_Python_ObjectBase_BufferProcs, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, - .tp_doc = "EDS Object" + .tp_doc = PyDoc_STR("EDS Object") }; static void EdsLib_Python_ObjectBase_dealloc(PyObject * obj) diff --git a/edslib/python/src/edslib_python_container.c b/edslib/python/src/edslib_python_container.c index 29973bced..223d7e7aa 100644 --- a/edslib/python/src/edslib_python_container.c +++ b/edslib/python/src/edslib_python_container.c @@ -84,7 +84,8 @@ PyTypeObject EdsLib_Python_ContainerIteratorType = .tp_traverse = EdsLib_Python_ContainerIterator_traverse, .tp_clear = EdsLib_Python_ContainerIterator_clear, .tp_iter = PyObject_SelfIter, - .tp_iternext = EdsLib_Python_ContainerIterator_iternext + .tp_iternext = EdsLib_Python_ContainerIterator_iternext, + .tp_doc = PyDoc_STR("EDS ContainerIteratorType") }; static void EdsLib_Python_ContainerIterator_dealloc(PyObject * obj) diff --git a/edslib/python/src/edslib_python_database.c b/edslib/python/src/edslib_python_database.c index 20189c9c0..4b396ae55 100644 --- a/edslib/python/src/edslib_python_database.c +++ b/edslib/python/src/edslib_python_database.c @@ -323,4 +323,3 @@ static PyObject *EdsLib_Python_Database_getentry(PyObject *obj, PyObject *key) return result; } - diff --git a/edslib/python/src/edslib_python_databaseentry.c b/edslib/python/src/edslib_python_databaseentry.c index bf15ffd5a..8aedd438c 100644 --- a/edslib/python/src/edslib_python_databaseentry.c +++ b/edslib/python/src/edslib_python_databaseentry.c @@ -1,5 +1,6 @@ /* * LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library * * Copyright (c) 2020 United States Government as represented by * the Administrator of the National Aeronautics and Space Administration. @@ -32,6 +33,7 @@ */ #include "edslib_python_internal.h" +#include static PyObject * EdsLib_Python_DatabaseEntry_new(PyTypeObject *obj, PyObject *args, PyObject *kwds); static int EdsLib_Python_DatabaseEntry_init(PyObject *obj, PyObject *args, PyObject *kwds); @@ -42,6 +44,17 @@ static int EdsLib_Python_DatabaseEntry_clear(PyObject *obj); static Py_ssize_t EdsLib_Python_DatabaseEntry_len(PyObject *obj); static PyObject * EdsLib_Python_DatabaseEntry_seq_item(PyObject *obj, Py_ssize_t idx); static PyObject * EdsLib_Python_DatabaseEntry_map_subscript(PyObject *obj, PyObject *subscr); +static PyObject * EdsLib_Python_DatabaseEntry_iter(PyObject *obj); + +static void EdsLib_Python_EnumEntryIterator_dealloc(PyObject * obj); +static int EdsLib_Python_EnumEntryIterator_traverse(PyObject *obj, visitproc visit, void *arg); +static int EdsLib_Python_EnumEntryIterator_clear(PyObject *obj); +static PyObject * EdsLib_Python_EnumEntryIterator_iternext(PyObject *obj); + +static void EdsLib_Python_ContainerEntryIterator_dealloc(PyObject * obj); +static int EdsLib_Python_ContainerEntryIterator_traverse(PyObject *obj, visitproc visit, void *arg); +static int EdsLib_Python_ContainerEntryIterator_clear(PyObject *obj); +static PyObject * EdsLib_Python_ContainerEntryIterator_iternext(PyObject *obj); static PySequenceMethods EdsLib_Python_DatabaseEntry_SequenceMethods = { @@ -55,6 +68,12 @@ static PyMappingMethods EdsLib_Python_DatabaseEntry_MappingMethods = .mp_subscript = EdsLib_Python_DatabaseEntry_map_subscript }; +static struct PyMemberDef EdsLib_Python_DatabaseEntry_members[] = +{ + {"Name", T_OBJECT_EX, offsetof(EdsLib_Python_DatabaseEntry_t, BaseName), READONLY, "Database Name" }, + {NULL} /* Sentinel */ +}; + PyTypeObject EdsLib_Python_DatabaseEntryType = { PyVarObject_HEAD_INIT(NULL, 0) @@ -64,13 +83,45 @@ PyTypeObject EdsLib_Python_DatabaseEntryType = .tp_new = EdsLib_Python_DatabaseEntry_new, .tp_as_sequence = &EdsLib_Python_DatabaseEntry_SequenceMethods, .tp_as_mapping = &EdsLib_Python_DatabaseEntry_MappingMethods, + .tp_members = EdsLib_Python_DatabaseEntry_members, .tp_init = EdsLib_Python_DatabaseEntry_init, .tp_repr = EdsLib_Python_DatabaseEntry_repr, .tp_traverse = EdsLib_Python_DatabaseEntry_traverse, .tp_clear = EdsLib_Python_DatabaseEntry_clear, .tp_base = &PyType_Type, + .tp_iter = EdsLib_Python_DatabaseEntry_iter, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, - .tp_doc = "EDS database entry" + .tp_doc = PyDoc_STR("EDS database entry") +}; + +PyTypeObject EdsLib_Python_EnumEntryIteratorType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = EDSLIB_PYTHON_ENTITY_NAME("EnumerationEntryIterator"), + .tp_basicsize = sizeof(EdsLib_Python_EnumerationIterator_t), + .tp_dealloc = EdsLib_Python_EnumEntryIterator_dealloc, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = EdsLib_Python_EnumEntryIterator_traverse, + .tp_clear = EdsLib_Python_EnumEntryIterator_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = EdsLib_Python_EnumEntryIterator_iternext, + .tp_doc = PyDoc_STR("EDS EnumerationIteratorType") +}; + +PyTypeObject EdsLib_Python_ContainerEntryIteratorType = +{ + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = EDSLIB_PYTHON_ENTITY_NAME("ContainerEntryIterator"), + .tp_basicsize = sizeof(EdsLib_Python_ContainerIterator_t), + .tp_dealloc = EdsLib_Python_ContainerEntryIterator_dealloc, + .tp_getattro = PyObject_GenericGetAttr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_traverse = EdsLib_Python_ContainerEntryIterator_traverse, + .tp_clear = EdsLib_Python_ContainerEntryIterator_clear, + .tp_iter = PyObject_SelfIter, + .tp_iternext = EdsLib_Python_ContainerEntryIterator_iternext, + .tp_doc = PyDoc_STR("EDS ContainerIteratorType") }; static void EdsLib_Python_DatabaseEntry_GetFormatCodes(char *buffer, const EdsLib_Python_Database_t *refdb, EdsLib_Id_t EdsId) @@ -393,6 +444,7 @@ PyTypeObject *EdsLib_Python_DatabaseEntry_GetFromEdsId_Impl(EdsLib_Python_Databa Py_INCREF(refdb); selfptr->dbent.EdsDb = refdb; selfptr->dbent.EdsId = EdsId; + selfptr->dbent.BaseName = PyUnicode_FromFormat("%s", EdsLib_DisplayDB_GetBaseName(refdb->GD, EdsId)); selfptr->dbent.EdsTypeName = PyUnicode_FromFormat("%s/%s", EdsLib_DisplayDB_GetNamespace(refdb->GD, EdsId), EdsLib_DisplayDB_GetBaseName(refdb->GD, EdsId)); @@ -434,6 +486,7 @@ static int EdsLib_Python_DatabaseEntry_traverse(PyObject *obj, visitproc visit, { EdsLib_Python_DatabaseEntry_t *self = (EdsLib_Python_DatabaseEntry_t *)obj; Py_VISIT(self->EdsDb); + Py_VISIT(self->BaseName); Py_VISIT(self->EdsTypeName); return EdsLib_Python_DatabaseEntryType.tp_base->tp_traverse(obj, visit, arg); } @@ -442,6 +495,7 @@ static int EdsLib_Python_DatabaseEntry_clear(PyObject *obj) { EdsLib_Python_DatabaseEntry_t *self = (EdsLib_Python_DatabaseEntry_t *)obj; Py_CLEAR(self->EdsDb); + Py_CLEAR(self->BaseName); Py_CLEAR(self->EdsTypeName); return EdsLib_Python_DatabaseEntryType.tp_base->tp_clear(obj); } @@ -599,6 +653,7 @@ static PyObject *EdsLib_Python_DatabaseEntry_seq_item(PyObject *obj, Py_ssize_t EdsLib_DataTypeDB_TypeInfo_t TypeInfo; EdsLib_DataTypeDB_GetTypeInfo(self->EdsDb->GD, self->EdsId, &TypeInfo); + /* * for containers, grab the entry name from the list, and then * return the corresponding attribute by that name. This two advantages: @@ -632,7 +687,6 @@ static PyObject *EdsLib_Python_DatabaseEntry_seq_item(PyObject *obj, Py_ssize_t { result = EdsLib_Python_ElementAccessor_CreateFromEntityInfo(&EntityInfo); } - Py_XDECREF(attribute); return result; @@ -685,3 +739,213 @@ Py_ssize_t EdsLib_Python_DatabaseEntry_GetMaxSize(PyTypeObject* objtype) return DerivInfo.MaxSize.Bytes; } +static PyObject * EdsLib_Python_DatabaseEntry_iter(PyObject *obj) +{ + EdsLib_Python_DatabaseEntry_t *dbent = (EdsLib_Python_DatabaseEntry_t *)obj; + EdsLib_Python_EnumerationIterator_t *EnumIter; + EdsLib_Python_ContainerIterator_t *ContainerIter; + EdsLib_DisplayHint_t DisplayHint; + PyObject *result; + + /* sanity check that the db entry is an EDS entry is iterable (Container or Enumeration) */ + DisplayHint = EdsLib_DisplayDB_GetDisplayHint(dbent->EdsDb->GD, dbent->EdsId); + switch(DisplayHint) + { + case EDSLIB_DISPLAYHINT_ENUM_SYMTABLE: + EnumIter = PyObject_GC_New(EdsLib_Python_EnumerationIterator_t, &EdsLib_Python_EnumEntryIteratorType); + + if (EnumIter == NULL) + { + return NULL; + } + + EnumIter->Index = 0; + Py_INCREF(obj); + EnumIter->refobj = obj; + + result = (PyObject *)EnumIter; + PyObject_GC_Track(result); + + break; + case EDSLIB_DISPLAYHINT_MEMBER_NAMETABLE: + /* sanity check that the db entry has a valid subentry list (it should for all containers) */ + if (dbent->SubEntityList == NULL || !PyList_Check(dbent->SubEntityList)) + { + return PyErr_Format(PyExc_TypeError, "%s is not an mappable EDS type", Py_TYPE(obj)->tp_name); + } + ContainerIter = PyObject_GC_New(EdsLib_Python_ContainerIterator_t, &EdsLib_Python_ContainerEntryIteratorType); + if (ContainerIter == NULL) + { + return NULL; + } + ContainerIter->Position = 0; + Py_INCREF(obj); + ContainerIter->refobj = obj; + result = (PyObject *)ContainerIter; + PyObject_GC_Track(result); + break; + default: + return PyErr_Format(PyExc_TypeError, "%s is not an EDS Iterable type (Container or Enumeration)", Py_TYPE(obj)->tp_name); + } + + return result; +} + + +static void EdsLib_Python_EnumEntryIterator_dealloc(PyObject * obj) +{ + EdsLib_Python_EnumerationIterator_t *self = (EdsLib_Python_EnumerationIterator_t*)obj; + PyObject_GC_UnTrack(self); + Py_XDECREF(self->refobj); + PyObject_GC_Del(self); +} + +static int EdsLib_Python_EnumEntryIterator_traverse(PyObject *obj, visitproc visit, void *arg) +{ + EdsLib_Python_EnumerationIterator_t *self = (EdsLib_Python_EnumerationIterator_t*)obj; + Py_VISIT(self->refobj); + return 0; +} + +static int EdsLib_Python_EnumEntryIterator_clear(PyObject *obj) +{ + EdsLib_Python_EnumerationIterator_t *self = (EdsLib_Python_EnumerationIterator_t*)obj; + Py_CLEAR(self->refobj); + return 0; +} + +static PyObject *EdsLib_Python_EnumEntryIterator_iternext(PyObject *obj) +{ + EdsLib_Python_EnumerationIterator_t *self = (EdsLib_Python_EnumerationIterator_t*)obj; + EdsLib_Python_DatabaseEntry_t *dbent = NULL; + const char * Label; + char LabelBuffer[64]; + intmax_t Value; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *result = NULL; + + do + { + if (self->refobj == NULL) + { + break; + } + + dbent = (EdsLib_Python_DatabaseEntry_t *)self->refobj; + Label = EdsLib_DisplayDB_GetEnumLabelByIndex(dbent->EdsDb->GD, dbent->EdsId, self->Index, LabelBuffer, sizeof(LabelBuffer)); + Value = EdsLib_DisplayDB_GetEnumValueByIndex(dbent->EdsDb->GD, dbent->EdsId, self->Index); + + if (strcmp(Label,"UNDEFINED") != 0) + { + key = PyUnicode_FromString(Label); + value = PyLong_FromLong(Value); + + if ((key == NULL) || (value == NULL)) + { + /* end */ + Py_CLEAR(self->refobj); + break; + } + + Py_INCREF(key); + Py_INCREF(value); + ++self->Index; + result = PyTuple_Pack(2, key, value); + } + } + while(0); + + Py_XDECREF(key); + Py_XDECREF(value); + + return result; +} + +static void EdsLib_Python_ContainerEntryIterator_dealloc(PyObject * obj) +{ + EdsLib_Python_ContainerIterator_t *self = (EdsLib_Python_ContainerIterator_t*)obj; + PyObject_GC_UnTrack(self); + Py_XDECREF(self->refobj); + PyObject_GC_Del(self); +} + +static int EdsLib_Python_ContainerEntryIterator_traverse(PyObject *obj, visitproc visit, void *arg) +{ + EdsLib_Python_ContainerIterator_t *self = (EdsLib_Python_ContainerIterator_t*)obj; + Py_VISIT(self->refobj); + return 0; +} + +static int EdsLib_Python_ContainerEntryIterator_clear(PyObject *obj) +{ + EdsLib_Python_ContainerIterator_t *self = (EdsLib_Python_ContainerIterator_t*)obj; + Py_CLEAR(self->refobj); + return 0; +} + +static PyObject *EdsLib_Python_ContainerEntryIterator_iternext(PyObject *obj) +{ + EdsLib_Python_ContainerIterator_t *self = (EdsLib_Python_ContainerIterator_t*)obj; + EdsLib_Python_DatabaseEntry_t *dbent = NULL; + + PyObject *str; + const char *keystr; + + EdsLib_DataTypeDB_EntityInfo_t CompInfo; + PyObject *sub_obj = NULL; + EdsLib_Python_DatabaseEntry_t *sub_dbent = NULL; + PyObject *key = NULL; + PyObject *dbname = NULL; + PyObject *entry = NULL; + PyObject *result = NULL; + + do + { + if (self->refobj == NULL) + { + break; + } + + dbent = (EdsLib_Python_DatabaseEntry_t *)self->refobj; + if (self->Position < PyList_GET_SIZE(dbent->SubEntityList)) + { + key = PyList_GET_ITEM(dbent->SubEntityList, self->Position); /* borrowed ref */ + } + + if (key == NULL) + { + /* end */ + Py_CLEAR(self->refobj); + break; + } + Py_INCREF(key); + + str = PyUnicode_AsEncodedString(key, "utf-8", "~E~"); + keystr = PyBytes_AS_STRING(str); + + if (EdsLib_DisplayDB_LocateSubEntity(dbent->EdsDb->GD, dbent->EdsId, keystr, &CompInfo) == EDSLIB_SUCCESS) + { + sub_obj = (PyObject *)EdsLib_Python_DatabaseEntry_GetFromEdsId_Impl(dbent->EdsDb, CompInfo.EdsId); + sub_dbent = (EdsLib_Python_DatabaseEntry_t *) sub_obj; + dbname = sub_dbent->EdsDb->DbName; + entry = sub_dbent->EdsTypeName; + } + + if ((entry == NULL) || (dbname == NULL)) + { + break; + } + ++self->Position; + Py_INCREF(dbname); + Py_INCREF(entry); + result = PyTuple_Pack(3, key, dbname, entry); + } + while(0); + + Py_XDECREF(key); + Py_XDECREF(dbname); + Py_XDECREF(entry); + + return result; +} diff --git a/edslib/python/src/edslib_python_internal.h b/edslib/python/src/edslib_python_internal.h index c3a35f9b5..d2a64d184 100644 --- a/edslib/python/src/edslib_python_internal.h +++ b/edslib/python/src/edslib_python_internal.h @@ -1,5 +1,6 @@ /* * LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library * * Copyright (c) 2020 United States Government as represented by * the Administrator of the National Aeronautics and Space Administration. @@ -64,6 +65,7 @@ typedef struct { PyHeapTypeObject type_base; EdsLib_Python_Database_t *EdsDb; + PyObject *BaseName; PyObject *EdsTypeName; EdsLib_Id_t EdsId; char FormatInfo[EDSLIB_PYTHON_FORMATCODE_LEN]; @@ -77,6 +79,13 @@ typedef struct PyObject* refobj; } EdsLib_Python_ContainerIterator_t; +typedef struct +{ + PyObject_HEAD + uint16_t Index; + PyObject* refobj; +} EdsLib_Python_EnumerationIterator_t; + typedef struct { PyObject_HEAD @@ -173,6 +182,8 @@ extern PyTypeObject EdsLib_Python_BufferType; extern PyTypeObject EdsLib_Python_AccessorType; extern PyTypeObject EdsLib_Python_PackedObjectType; extern PyTypeObject EdsLib_Python_ContainerIteratorType; +extern PyTypeObject EdsLib_Python_EnumEntryIteratorType; +extern PyTypeObject EdsLib_Python_ContainerEntryIteratorType; extern PyTypeObject EdsLib_Python_ObjectBaseType; extern PyTypeObject EdsLib_Python_ObjectNumberType; diff --git a/edslib/python/src/edslib_python_setup.c b/edslib/python/src/edslib_python_setup.c index 23d2decc2..914ebd504 100644 --- a/edslib/python/src/edslib_python_setup.c +++ b/edslib/python/src/edslib_python_setup.c @@ -1,5 +1,6 @@ /* * LEW-19710-1, CCSDS SOIS Electronic Data Sheet Implementation + * LEW-20211-1, Python Bindings for the Core Flight Executive Mission Library * * Copyright (c) 2020 United States Government as represented by * the Administrator of the National Aeronautics and Space Administration. @@ -78,6 +79,8 @@ PyObject* EdsLib_Python_CreateModule(void) PyType_Ready(&EdsLib_Python_AccessorType) != 0 || PyType_Ready(&EdsLib_Python_PackedObjectType) != 0 || PyType_Ready(&EdsLib_Python_ContainerIteratorType) != 0 || + PyType_Ready(&EdsLib_Python_EnumEntryIteratorType) != 0 || + PyType_Ready(&EdsLib_Python_ContainerEntryIteratorType) != 0 || PyType_Ready(&EdsLib_Python_ObjectBaseType) != 0 || PyType_Ready(&EdsLib_Python_ObjectScalarType) != 0 || PyType_Ready(&EdsLib_Python_ObjectNumberType) != 0 ||