diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 00000000..24f386e4 --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,16 @@ +name: Docker Image CI + +on: + push: + branches: [ "main", "**" ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag sceptre-bennu:$(date +%s) diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 00000000..2e105378 --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,48 @@ +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ['**'] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v3 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 00000000..5a160013 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,107 @@ +# create python build image +FROM ubuntu:20.04 AS pybuilder + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt update \ + && apt install -y \ + build-essential cmake git wget python3-dev python3-pip \ + libfreetype6-dev liblapack-dev libboost-dev \ + && rm -rf /var/lib/apt/lists/* + +# setup ZMQ +ENV ZMQ_VERSION 4.3.4 +RUN wget -O zmq.tgz https://github.com/zeromq/libzmq/releases/download/v4.3.4/zeromq-4.3.4.tar.gz \ + && mkdir -p /tmp/zmq \ + && tar -C /tmp/zmq -xvzf zmq.tgz \ + && rm zmq.tgz \ + && cd /tmp/zmq/zeromq-${ZMQ_VERSION} \ + && ./configure --enable-drafts \ + && make -j$(nproc) install + +# setup Helics (needed for pybennu) +ENV HELICS_VERSION 2.7.1 +RUN wget -O helics.tgz https://github.com/GMLC-TDC/HELICS/releases/download/v${HELICS_VERSION}/Helics-v${HELICS_VERSION}-source.tar.gz \ + && mkdir -p /tmp/helics \ + && tar -C /tmp/helics -xzf helics.tgz \ + && rm helics.tgz \ + && mkdir -p /tmp/helics/build && cd /tmp/helics/build \ + && cmake -D HELICS_USE_SYSTEM_ZEROMQ_ONLY=ON .. \ + && make -j$(nproc) install + +RUN python3 -m pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org pyzmq~=20.0.0 --install-option=--enable-drafts + +RUN wget -O pyhelics.tgz https://github.com/GMLC-TDC/pyhelics/releases/download/v${HELICS_VERSION}/helics-${HELICS_VERSION}.tar.gz \ + && mkdir -p /tmp/pyhelics \ + && tar -C /tmp/pyhelics -xzf pyhelics.tgz \ + && rm pyhelics.tgz \ + && cd /tmp/pyhelics/helics-${HELICS_VERSION} \ + && sed -i 's/helics-apps/helics-apps~=2.7.1/' setup.py \ + && python3 -m pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org . + +#DEBUG build +#ADD docker/vendor /tmp/bennu/vendor +#WORKDIR /tmp/bennu/vendor/helics-helper +#RUN python3 -m pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org . + +# install Python bennu package +ADD src/pybennu /tmp/bennu/src/pybennu +WORKDIR /tmp/bennu/src/pybennu +RUN python3 -m pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org . + +# create final image +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND noninteractive + +RUN apt update \ + && apt install -y \ + # bennu + build-essential ca-certificates cmake cmake-curses-gui \ + git wget pkg-config libasio-dev libsodium-dev \ + libboost-system-dev libboost-filesystem-dev \ + libboost-thread-dev libboost-serialization-dev \ + libboost-date-time-dev libboost-program-options-dev \ + libboost-iostreams-dev libsodium23 libfreetype6 \ + liblapack3 libzmq5-dev \ + # fpm + libffi-dev ruby-dev ruby-ffi \ + # python + python3-dev python3-pip python3-setuptools python3-wheel \ + && rm -rf /var/lib/apt/lists/* + +# setup Go +#ENV GOLANG_VERSION 1.16.5 +#RUN wget -O go.tgz https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \ +# && tar -C /usr/local -xzf go.tgz && rm go.tgz +#ENV GOPATH /go +#ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH + +# copy over custom-built libraries +COPY --from=pybuilder /usr/local /usr/local +ENV LD_LIBRARY_PATH /usr/local/lib:/usr/local/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH} + +# copy over bennu source code +ADD cmake /tmp/bennu/cmake +ADD CMakeLists.txt /tmp/bennu/CMakeLists.txt +ADD src/bennu /tmp/bennu/src/bennu +ADD src/deps /tmp/bennu/src/deps +#ADD src/gobennu /tmp/bennu/src/gobennu +ADD test /tmp/bennu/test + +#set necessary vars +#ENV GOPROXY "http://proxy.golang.org" +#ENV GONOSUMDB "proxy.golang.org/*,github.com,github.com/*,gopkg.in,gopkg.in/*,golang.org/*,golang.org" +#ENV GOPRIVACY "proxy.golang.org/*,github.com,github.com/*,gopkg.in,gopkg.in/*,golang.org/*,golang.org" +#ENV GOINSECURE "proxy.golang.org/*,github.com,github.com/*,gopkg.in,gopkg.in/*,golang.org/*,golang.org" + +# install C++ and Golang bennu package +WORKDIR /tmp/bennu/build +RUN cmake -D BUILD_GOBENNU=OFF ../ && make -j$(nproc) install \ + && rm -rf /tmp/* + +RUN gem install fpm +RUN pip3 install --trusted-host pypy.org --trusted-host files.pythonhosted.org -U aptly-ctl pip setuptools twine wheel + +WORKDIR /root +CMD /bin/bash diff --git a/src/bennu/executables/bennu-simulink-provider/solver/build.sh b/src/bennu/executables/bennu-simulink-provider/solver/modelhooks/build.sh similarity index 100% rename from src/bennu/executables/bennu-simulink-provider/solver/build.sh rename to src/bennu/executables/bennu-simulink-provider/solver/modelhooks/build.sh diff --git a/src/bennu/executables/bennu-simulink-provider/solver/clean.sh b/src/bennu/executables/bennu-simulink-provider/solver/modelhooks/clean.sh similarity index 100% rename from src/bennu/executables/bennu-simulink-provider/solver/clean.sh rename to src/bennu/executables/bennu-simulink-provider/solver/modelhooks/clean.sh diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelHooks.c b/src/bennu/executables/bennu-simulink-provider/solver/modelhooks/modelHooks.c similarity index 100% rename from src/bennu/executables/bennu-simulink-provider/solver/modelHooks.c rename to src/bennu/executables/bennu-simulink-provider/solver/modelhooks/modelHooks.c diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelHooks.h b/src/bennu/executables/bennu-simulink-provider/solver/modelhooks/modelHooks.h similarity index 100% rename from src/bennu/executables/bennu-simulink-provider/solver/modelHooks.h rename to src/bennu/executables/bennu-simulink-provider/solver/modelhooks/modelHooks.h diff --git a/src/bennu/executables/bennu-simulink-provider/solver/publishPoints.txt b/src/bennu/executables/bennu-simulink-provider/solver/modelhooks/publishPoints.txt similarity index 100% rename from src/bennu/executables/bennu-simulink-provider/solver/publishPoints.txt rename to src/bennu/executables/bennu-simulink-provider/solver/modelhooks/publishPoints.txt diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/build.sh b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/build.sh new file mode 100755 index 00000000..76e2aa0e --- /dev/null +++ b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/build.sh @@ -0,0 +1,76 @@ +# TODO: make this script take as input the .slx to build + +if [ $# -ne 1 ]; then + echo "Usage: ./bulid processModle.slx" + exit +fi + +fullfile=$(basename -- "$1"); +echo $fullfile + +filename="${fullfile%.*}"; +echo $filename; + +extension="${fullfile##*.}"; +echo $extension; + +buildfolder="${filename}_grt_rtw" +echo $buildfolder + + +if [ $extension != "slx" ]; then + echo "Must supply a .slx file" + exit +fi + +# FYI: the model name is case sensitive +modelname=$filename +echo $modelname + +apifile="${buildfolder}/${modelname}_capi.c" +echo $apifile + +command -v matlab 2>&1 > /dev/null; +if [ $? -eq 0 ]; then + echo "matlab is installed, proceeding to build Simulink process model..." + # TODO: Research if there is the ability to hook the code from the command line + matlab -nojvm -nodisplay -nodesktop -r "warning off; rtwbuild('${modelname}'); quit();" +else + echo "matlab not installed, skipping Simulink process model build..." +fi + +# mv inputs.txt inputs.bak +# mv outputs.txt outputs.bak +# +# # TODO: Make sure this still works with other solvers +# cat $apifile \ +# | cut -d\" -f2 \ +# | cut -d\/ -f3 \ +# | cut -d" " -f2 \ +# | grep -Ev "^(sim)" \ +# | grep -E "(_AI|_DI)" \ +# | sort \ +# | uniq \ +# >> inputs.txt \ +# ; +# +# cat $apifile \ +# | cut -d\" -f2 \ +# | cut -d\/ -f3 \ +# | grep -Ev "(sim|Scale)" \ +# | grep -E "(_AO|_DO)" \ +# | sort \ +# | uniq \ +# >> outputs.txt \ +# ; +# +# cat $apifile \ +# | cut -d\" -f2 \ +# | cut -d\/ -f3 \ +# | cut -d" " -f2 \ +# | grep -Ev "^(sim)" \ +# | grep -E "(_PANEL)" \ +# | sort \ +# | uniq \ +# >> outputs.txt \ +# ; diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/clean.sh b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/clean.sh new file mode 100755 index 00000000..8b18c3f6 --- /dev/null +++ b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/clean.sh @@ -0,0 +1,31 @@ +for filepath in ./*.slx; do + # set required variables + #echo $filepath; + fullfile=$(basename -- "$filepath"); + #echo $fullfile; + filename="${fullfile%.*}"; + #echo $filename; + extension="${fullfile##*.}"; + #echo $extension; + buildfolder="$filename""_grt_rtw" + #echo $buildfolder + + # rm build folders + rm -rf $buildfolder + rm -rf slprj + + # rm extra files + if [ -f "${filename}.slxc" ]; then + rm "${filename}.slxc" + fi + + if [ -f "${filename}.mat" ]; then + rm "${filename}.mat" + fi + + # rm binary file + if [ -f $filename ]; then + rm $filename + fi +done; + diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.c b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.c new file mode 100644 index 00000000..e83fd40e --- /dev/null +++ b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.c @@ -0,0 +1,636 @@ +#include "modelHooks.h" + + +/* ********************************** + * Model Hooks (called from Simulink) + ********************************** */ +void init(rtwCAPI_ModelMappingInfo* _modelMap) +{ + setbuf(stdout, NULL); /* Disable stdout buffering */ + + if (-1 != access(DEBUG_FILE, F_OK)) { + DEBUG = true; + } + if (-1 != access(DEBUG_VERBOSE_FILE, F_OK)) { + DEBUG = true; + DEBUG_VERBOSE = true; + } + + printf("Info: Model initialization hooked\n"); + + /* Simulink Model */ + modelMap = _modelMap; + if (NULL == modelMap) { + printf("Fatal: Model Mapping Info is NULL\n"); + exit(EXIT_ERROR); + } + + /* Value to Data Type Mapping */ + dataTypeMap = rtwCAPI_GetDataTypeMap(modelMap); + if (NULL == dataTypeMap) { + printf("Fatal: Data Type Map is NULL\n"); + exit(EXIT_ERROR); + } + + /* Value to Dimension Metadata Mapping */ + dimensionMap = rtwCAPI_GetDimensionMap(modelMap); + dimensionArray = rtwCAPI_GetDimensionArray(modelMap); + if ((NULL == dimensionMap) || (NULL == dimensionArray)) { + printf("Fatal: Data Dimension Info is NULL\n"); + exit(EXIT_ERROR); + } + + /* Value to Fixed-Point Metadata Mapping */ + fixedPointMap = rtwCAPI_GetFixPtMap(modelMap); + if (NULL == fixedPointMap) { + printf("Fatal: Fix Point Map is NULL\n"); + exit(EXIT_ERROR); + } + + /* Value Data Address */ + dataAddressMap = rtwCAPI_GetDataAddressMap(modelMap); + if (NULL == dataAddressMap) { + printf("Fatal: Data Address Map is NULL\n"); + exit(EXIT_ERROR); + } + + /* Model Signals - aka PublishPoints */ + numModelSignals = rtwCAPI_GetNumSignals(modelMap); + modelSignals = rtwCAPI_GetSignals(modelMap); + if (NULL == modelSignals) { + printf("Fatal: Signal Mapping Info is NULL\n"); + exit(EXIT_ERROR); + } + + /* Model Parameters - aka UpdatePoints */ + numModelParameters = rtwCAPI_GetNumBlockParameters(modelMap); + modelParameters = rtwCAPI_GetBlockParameters(modelMap); + if (NULL == modelParameters) { + printf("Fatal: Parameter Mapping Info is NULL\n"); + exit(EXIT_ERROR); + } + + if (DEBUG) { + printModelMemberCounts(); + } + + /* Read PublishPoints file into memory */ + numPublishPoints = 128; /*number will be set to the correct count after reading*/ + readFileToMemory(PUBLISH_POINTS_FILE, &publishPoints, &numPublishPoints); + + printf("Info: Read %d PublishPoints\n", numPublishPoints); + + /* Create a semaphores in the locked state (last arg = 0)*/ + publishSemaphore = sem_open(PUBLISH_SEM, O_CREAT, 0644, 0); + if((void*)-1 == publishSemaphore) { + printf("Fatal: Unable to create publish semaphore\n"); + exit(EXIT_ERROR); + } + updatesSemaphore = sem_open(UPDATES_SEM, O_CREAT, 0644, 0); + if((void*)-1 == updatesSemaphore) { + printf("Fatal: Unable to create updates semaphore\n"); + exit(EXIT_ERROR); + } + + /* Create a FIFO queue for passing UpdatePoints from provider to solver */ + unlink(UPDATES_FIFO); /* just incase fifo still exists from last run */ + if (-1 == mkfifo(UPDATES_FIFO, 0666)) { + printf("Fatal: Unable to create update FIFO at %s\n", UPDATES_FIFO); + exit(EXIT_ERROR); + } + updatesFifo = open(UPDATES_FIFO, O_RDONLY | O_TRUNC | O_NONBLOCK); + if (-1 == updatesFifo) { + printf("Fatal: Unable to open updates FIFO at %s for non-blocked reading\n", UPDATES_FIFO); + exit(EXIT_ERROR); + } + + /* Shared Memory Count Blocks */ + numPublishPointsShmId = createSharedMemory(NUM_PUBLISH_POINTS_SHM_KEY, sizeof(unsigned int)); + numPublishPointsShmAddress = attachSharedMemory(numPublishPointsShmId); + sprintf(numPublishPointsShmAddress, "%u", numPublishPoints); + + /* Shared Memory Blocks */ + publishPointsShmSize = numPublishPoints * MAX_MSG_LEN; + publishPointsShmId = createSharedMemory(PUBLISH_POINTS_SHM_KEY, publishPointsShmSize); + publishPointsShmAddress = attachSharedMemory(publishPointsShmId); + publishState(); + + /* Unlock semaphores */ + sem_post(publishSemaphore); + sem_post(updatesSemaphore); + + /* Shared Memory Block for time in-order to calculate the external execution time outside + of the step function. Set initial times to zero refasan*/ + t_oldSim = 0; t_oldStart = 0; + ns2ms = 1e-3; s2ms = 1e+6; + expectedInterval = 0; jitter = 0; + currentTime = 0; + + timeShmId = createSharedMemory(TIME_SHM_KEY, sizeof(long)); + timeShmAddress = attachSharedMemory(timeShmId); + sprintf(timeShmAddress, "%lu", 0); + + clock_gettime(CLOCK_REALTIME, &initial); +} + + +void step(time_T* time) +{ + clock_gettime(CLOCK_REALTIME, &start); + + // Calculate the simulation time step by subtracting the current simulation time + // from the previous simulation time + majorTimeStep = (*time - t_oldSim)*s2ms; + t_oldSim = *time; + // Keep track of the current simulation time + expectedInterval = expectedInterval + majorTimeStep; + + // .tv_nsec returns the number of nanosecounds in the current second + // therefore if t_oldStart is less than the current start time + // a second has elapsed. Add one micro second is added to the current time + if (t_oldStart > (double)(start.tv_nsec)) { + currentTime = currentTime + 1e+6; + } + t_oldStart = start.tv_nsec; + jitter = expectedInterval - currentTime; + + isIntegral = (*time - (int)*time == 0); + isInterval = ((int)*time % DEBUG_PRINT_INTERVAL == 0); + if (isIntegral && isInterval) { + printf("[time: %f]\n", *time); + char* shmPtr = publishPointsShmAddress; + for (int i = 0; i < numPublishPoints; i++) { + printf("PublishPoint: %s\n", shmPtr); + fflush(stdout); + shmPtr += MAX_MSG_LEN; + } + /* NOTE: Whoever is going to be reading the GroundTruth.txt file should also + * acquire the same semaphores to prevent reading it in an incomplete state. + * This is locked by the PublishPoints Semaphore because PublishPoints are + * a subset of the GroundTruth, whereas UpdatePoints are not included. */ + sem_wait(publishSemaphore); + printGroundTruth(); + sem_post(publishSemaphore); + + } + + // Using rate trasition blocks in simulink guarantees that the majorTimeStep + // will be zero for the rate specified in the rate transition block + if (majorTimeStep == 0) { + sem_wait(publishSemaphore); + publishState(); + sem_post(publishSemaphore); + } + + sem_wait(updatesSemaphore); + applyUpdates(); + sem_post(updatesSemaphore); + + clock_gettime(CLOCK_REALTIME, &finish); + internalExeTime = (double)(finish.tv_nsec - start.tv_nsec)*ns2ms; + if (internalExeTime < 0){ + internalExeTime = 0; + } + externalExeTime = (double)(start.tv_nsec - strtol(timeShmAddress,&eptr,10))*ns2ms; + if (externalExeTime < 0){ + externalExeTime = 0; + } + + if (isIntegral && isInterval) { + printf("%f majorTimeStep [Mico-seconds]\n",majorTimeStep); + printf("%f internalExeTime [Mico-seconds]\n",internalExeTime); + printf("%f externalExeTime [Micro-seconds]\n",externalExeTime); + printf("%f jitter [Micro-seconds]\n",jitter); + fflush(stdout); + } + + wait = majorTimeStep - internalExeTime - externalExeTime + jitter; + + if (*time == 0) { + wait = majorTimeStep; + }else if (wait < 0){ + wait = 0; + } + + usleep((unsigned int)(wait)); + clock_gettime(CLOCK_REALTIME, &finish); + sprintf(timeShmAddress, "%lu", finish.tv_nsec); +} + + +void term() +{ + /* NOTE: This never gets called if you ^c out of the solver */ + int i; + + printf("Info: Model termination hooked\n"); + + sem_wait(publishSemaphore); + sem_wait(updatesSemaphore); + + destroySharedMemory(numPublishPointsShmId, numPublishPointsShmAddress); + destroySharedMemory(publishPointsShmId, publishPointsShmAddress); + + sem_close(publishSemaphore); + sem_unlink(PUBLISH_SEM); + + sem_close(updatesSemaphore); + sem_unlink(UPDATES_SEM); + + close(updatesFifo); + unlink(UPDATES_FIFO); + + for (i = 0; i < numPublishPoints; i++) { + if (NULL != publishPoints[i]) { + free(publishPoints[i]); + } + } + if (NULL != publishPoints) { + free(publishPoints); + } +} + + + +/* ******************************* + * Start Model Interaction Helpers + ******************************* */ +void publishState() +{ + int i; + char* shmPtr = publishPointsShmAddress; + + for (i = 0; i < numPublishPoints; i++) { + char* dto = getModelSignalValue(publishPoints[i]); + + strcpy(shmPtr, dto); + shmPtr += MAX_MSG_LEN; + + /*dto Memory is allocated in getModelSignalValue*/ + if (NULL != dto) { + free(dto); + } + } +} + +void applyUpdates() +{ + char *tag, *type, *val; + char *updateMessage = (char*)calloc(MAX_MSG_LEN, sizeof(char)); + if (NULL == updateMessage) { + printf("Fatal: Not enough memory for updateMessage allocation\n"); + exit(EXIT_ERROR); + } + + while (read(updatesFifo, updateMessage, MAX_MSG_LEN) > 0) { + tag = strtok(updateMessage, ":"); + type = strtok(NULL, ":"); + val = strtok(NULL, ":"); + + if (DEBUG) { + printf("updating tag: %s, type: %s, val: %s\n", tag, type, val); + } + + updateModelParameterValue(tag, val); + + memset(updateMessage, 0, (MAX_MSG_LEN * sizeof(char))); + } + + if (NULL != updateMessage) { + free(updateMessage); + } + +} + + +char* getModelSignalValue(const char* match) +{ + uint_T signalIdx; + const char* signalPath; + + uint16_T dataTypeIdx; + uint8_T slDataId; + + uint16_T fixedPointMapIdx; + real_T slope = 1.0; + real_T bias = 0.0; + + uint_T addressIdx; + void* signalAddress; + + char* dto = (char*)calloc(MAX_MSG_LEN, sizeof(char)); + char* dtoPtr = dto; + if (NULL == dto) { + printf("Fatal: Not enough memory for next dto allocation\n"); + exit(EXIT_ERROR); + } + + for (signalIdx = 0; signalIdx < numModelSignals; signalIdx++) { + signalPath = rtwCAPI_GetSignalBlockPath(modelSignals, signalIdx); + if (NULL != strstr(signalPath, match)) { + break; + } + } + + dataTypeIdx = rtwCAPI_GetSignalDataTypeIdx(modelSignals, signalIdx); + slDataId = rtwCAPI_GetDataTypeSLId(dataTypeMap, dataTypeIdx); + + fixedPointMapIdx = rtwCAPI_GetSignalFixPtIdx(modelSignals, signalIdx); + if (fixedPointMapIdx > 0) { + real_T fracSlope = rtwCAPI_GetFxpFracSlope(fixedPointMap, fixedPointMapIdx); + int8_T exponent = rtwCAPI_GetFxpExponent(fixedPointMap, fixedPointMapIdx); + + slope = fracSlope * pow(2.0, exponent); + } + + addressIdx = rtwCAPI_GetSignalAddrIdx(modelSignals, signalIdx); + signalAddress = (void *)rtwCAPI_GetDataAddress(dataAddressMap, addressIdx); + if (NULL == signalAddress) { + printf("Fatal: Signal Data Address is NULL\n"); + exit(EXIT_ERROR); + } + + dtoPtr += sprintf(dtoPtr, "%s:", match); + if (DEBUG_VERBOSE) { + printf("%s:", match); + } + + switch (slDataId) { + case SS_BOOLEAN: + dtoPtr += sprintf(dtoPtr, "BOOLEAN:%d", *((boolean_T *)signalAddress)); + if (DEBUG_VERBOSE) { + printf("BOOLEAN:%d\n", *((boolean_T *)signalAddress)); + } + break; + case SS_DOUBLE: + dtoPtr += sprintf(dtoPtr, "DOUBLE:%f", *((real_T *)signalAddress)); + if (DEBUG_VERBOSE) { + printf("DOUBLE:%f\n", *((real_T *)signalAddress)); + } + break; + default: + printf("Warn: UNHANDLED TYPE - unknown value type logic needed \n"); + } + + return dto; +} + + +void updateModelParameterValue(const char* match, char* newValue) +{ + + uint_T parameterIdx; + bool parameterFound; + const char* parameterPath; + + uint16_T dataTypeIdx; + uint8_T slDataId; + + uint_T addressIdx; + void* parameterAddress; + + real_T* doubleParam; + double newDoubleParam; + boolean_T* boolParam; + bool newBoolParam; + + /* TODO: can optimize here by storing the Idxs in publishPoints with tag + * - As long as we can be sure they won't change over time */ + parameterFound = false; + for (parameterIdx = 0; parameterIdx < numModelParameters; parameterIdx++) { + parameterPath = rtwCAPI_GetBlockParameterBlockPath(modelParameters, parameterIdx); + if (NULL != strstr(parameterPath, match)) { + parameterFound = true; + break; + } + } + + if (!parameterFound) { + if (DEBUG) { + printf("%s parameter not found, no updates will be performed\n", match); + } + return; + } + + dataTypeIdx = rtwCAPI_GetBlockParameterDataTypeIdx(modelParameters, parameterIdx); + slDataId = rtwCAPI_GetDataTypeSLId(dataTypeMap, dataTypeIdx); + + addressIdx = rtwCAPI_GetBlockParameterAddrIdx(modelParameters, parameterIdx); + parameterAddress = (void *)rtwCAPI_GetDataAddress(dataAddressMap, addressIdx); + if (NULL == parameterAddress) { + printf("Fatal: Parameter Data Address is NULL\n"); + exit(EXIT_ERROR); + } + + if (DEBUG) { + printf("%s:", parameterPath); + } + + switch (slDataId) { + case SS_DOUBLE: + doubleParam = (real_T *)parameterAddress; + newDoubleParam = strtod(newValue, NULL); + if (errno == ERANGE) { + printf("Error: could not convert '%s' to a double", newValue); + } + if (DEBUG) { + printf("DOUBLE:%f->%f\n", *doubleParam, newDoubleParam); + } + *doubleParam = newDoubleParam; + break; + case SS_BOOLEAN: + boolParam = (boolean_T *)parameterAddress; + newBoolParam = strtol(newValue, NULL, 2); + if (errno == ERANGE) { + printf("Error: could not convert '%s' to a boolean", newValue); + } + if (DEBUG) { + printf("BOOLEAN:%d->%d\n", *boolParam, newBoolParam); + } + *boolParam = newBoolParam; + break; + default: + printf("Warn: UNHANDLED TYPE - unknown value type logic needed \n"); + } +} + + + +/* *************************** + * Start Shared Memory Helpers + *************************** */ +int createSharedMemory(key_t key, int bytes) +{ + int shmid = shmget(key, bytes, IPC_CREAT | 0666); + if (shmid < 0) { + printf("Fatal: Failed to get shared memory for key %d\n", key); + exit(EXIT_ERROR); + } + + printf("Info: Created shared memory with key %d\n", key); + return shmid; +} + + +char* attachSharedMemory(int shmid) +{ + char *mem = (char*)shmat(shmid, NULL, 0); + if (mem == (char*)-1){ + printf("Fatal: Failed to attach to shared memory\n"); + exit(EXIT_ERROR); + } + + printf("Info: Attached to shared memory\n"); + return mem; +} + + +void destroySharedMemory(int shmid, char* shmaddr) +{ + shmdt(shmaddr); + shmctl(shmid, IPC_RMID, NULL); + + printf("Info: Destroyed shared memory\n"); +} + + + +/* ******************* + * Start Misc. Helpers + ******************* */ +void readFileToMemory(const char* fileName, char** *array, unsigned int *num) +{ + FILE* fp = NULL; + size_t lineLen = 0; + ssize_t bytesRead = 0; + int linesRead = 0; + int k; + + fp = fopen(fileName, "r"); + if (NULL == fp) { + printf("Fatal: Cannot open/find file: %s\n", fileName); + exit(EXIT_ERROR); + } + + *array = (char **)malloc(sizeof(char*) * (*num)); + if (NULL == *array) { + printf("Fatal: Cannot allocate space for memory array\n"); + exit(EXIT_ERROR); + } + + for (k = 0; k < *num; k++) { + (*array)[k] = (char*)malloc(sizeof(char*) * MAX_MSG_LEN);; + if (NULL == (*array)[k]) { + printf("Fatal: Cannot allocate space for memory subarray\n"); + exit(EXIT_ERROR); + } + } + + while ((bytesRead = getline(&(*array)[linesRead], &lineLen, fp)) != -1) { + if (linesRead >= *num) { + /* Allocate more space in the array */ + *num = *num * 2; + *array = (char **)realloc(*array, sizeof(char*) * *num); + if (NULL == *array) { + printf("Fatal: Cannot re-allocate space for more memory"); + exit(EXIT_ERROR); + } + } + + (*array)[linesRead][bytesRead-1] = 0; /*remove newline character*/ + + linesRead++; + } + + *num = linesRead; + + fclose(fp); +} + +void printGroundTruth() +{ + FILE *groundTruth; + + uint_T signalIdx; + const char* signalPath; + char* signalPathCopy[MAX_MSG_LEN]; + char* signalNamePtr; + char* signalName; + uint16_T dataTypeIdx; + uint8_T slDataId; + uint_T addressIdx; + void* signalAddress; + + char* dto = (char*)calloc(MAX_MSG_LEN, sizeof(char)); + char* dtoPtr = dto; + if(NULL == dto){ + printf("Fatal: Not enough memory for ground truth dto allocation\n"); + exit(EXIT_ERROR); + } + + groundTruth = fopen(GROUND_TRUTH_FILE, "w"); + if (NULL == groundTruth) { + printf("Fatal: Cannot create/truncate file: %s\n", GROUND_TRUTH_FILE); + exit(EXIT_ERROR); + } + + for (signalIdx = 0; signalIdx < numModelSignals; signalIdx++) { + signalPath = rtwCAPI_GetSignalBlockPath(modelSignals, signalIdx); + if (NULL == strstr(signalPath, GROUND_TRUTH_FILTER)) { + /* Ground truth filter not matched, do not include signal in groundTruth */ + continue; + } + + dataTypeIdx = rtwCAPI_GetSignalDataTypeIdx(modelSignals, signalIdx); + slDataId = rtwCAPI_GetDataTypeSLId(dataTypeMap, dataTypeIdx); + + addressIdx = rtwCAPI_GetSignalAddrIdx(modelSignals, signalIdx); + signalAddress = (void *)rtwCAPI_GetDataAddress(dataAddressMap, addressIdx); + if (NULL == signalAddress) { + printf("Fatal: Signal Data Address is NULL\n"); + exit(EXIT_ERROR); + } + + /* strip off the full path info, leaving only signal name */ + strncpy(signalPathCopy, signalPath, MAX_MSG_LEN); + signalNamePtr = strtok(signalPathCopy, "/"); + while(NULL != signalNamePtr){ + signalName = signalNamePtr; + signalNamePtr = strtok(NULL, "/"); + } + + dtoPtr += sprintf(dtoPtr, "%s:", signalName); + switch (slDataId) { + case SS_BOOLEAN: + dtoPtr += sprintf(dtoPtr, "BOOLEAN:%d", *((boolean_T *)signalAddress)); + break; + case SS_DOUBLE: + dtoPtr += sprintf(dtoPtr, "DOUBLE:%f", *((real_T *)signalAddress)); + break; + default: + printf("Warn: UNHANDLED CODE - unknown value type logic needed \n"); + } + + fprintf(groundTruth, "%s\n", dto); + + memset(dto, 0, (MAX_MSG_LEN * sizeof(char))); + dtoPtr = dto; + } + + fclose(groundTruth); +} + + +void printModelMemberCounts() +{ + uint_T numModelParams = rtwCAPI_GetNumModelParameters(modelMap); + uint_T numBlockParams = rtwCAPI_GetNumBlockParameters(modelMap); + uint_T numStates = rtwCAPI_GetNumStates(modelMap); + uint_T numModelSignals = rtwCAPI_GetNumSignals(modelMap); + + printf("Number of Model Parameters %d\n", numModelParams); + printf("Number of Block Parameters %d\n", numBlockParams); + printf("Number of States %d\n", numStates); + printf("Number of Signals %d\n", numModelSignals); +} + diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.h b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.h new file mode 100644 index 00000000..72657b86 --- /dev/null +++ b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/modelHooks.h @@ -0,0 +1,238 @@ +/* + * ************************ + * Hooking Model Code Setup + * ************************ + * + * NOTE: Compile the model once before hooking so that MODEL_NAME can be found + * in the compiled model c code. (const object under 'Real-time model' comment) + * (e.g. MODEL_NAME = CoDCon_Feb18_FirstRun_update_M) + * + * Header File: + * #include "modelHooks.h" + * + * Source Files: + * modelHooks.c + * + * Libraries: + * /usr/lib/x86_64-linux-gnu/libpthread.so + * (used for semaphores) + * + * Initialize Function: + * rtwCAPI_ModelMappingInfo *mmi = &(rtmGetDataMapInfo(MODEL_NAME).mmi); + * init(mmi); + * + * System Outputs (Drag and drop from MATLAB 'custcode' command into model): + * - Declaration Code: + * time_T* time = rtmGetTPtr(MODEL_NAME); + * - Execution Code: + * step(time); + * + * Terminatie Function: + * term(); + */ + +/* + * ************* + * Sync DTO Info + * ************* + * + * NOTE: Sometimes if the solver crashes the shared memory will still exist and + * be locked. To fix this use the following commands: + * ```ipcs``` Lists the shared memory segments that exists and their shmid + * ```ipcrm``` Removes shared memory segments, either -a all or -m by shmid + * + * NOTE: In a similar fashion, if you need to manually remove the FIFO file, + * use the ```unlink``` command + * + * PublishPoint Shared Memory DTO Format: + * "name:type:value" + * (e.g. StripMixer1_VFD_SpeedFeedback_AI:DOUBLE:0.88) + * + * UpdatePoint FIFO DTO Format: + * "name:type:value" + * (e.g. PLC - plc_StripMixer8_VFD_RunEnable_DO:BOOLEAN:0) + * + * Parts: + * - name: + * The name of the I/O point. + * (e.g. StripMixer1_VFD_SpeedFeedback_AI) + * (see publishPoints.txt and updatePoints.txt for a list of I/O points) + * Can provide field name with '.' + * (e.g. StripMixer1_VFD_SpeedFeedback_AI.speed) + * (if no field name is provided it will be set to processModelIO in filter) + * - type: + * The data type of the I/O point. + * (e.g. DOUBLE) + * - value: + * The value of the I/O point. + * (e.g. 0.88) + */ + +#ifndef MODEL_HOOKS +#define MODEL_HOOKS + +/* For real-time code execution in step() function */ +#define __USE_POSIX199309 +#define _POSIX_C_SOURCE 199309L + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "rtw_modelmap.h" +#include "builtin_typeid_types.h" +#include "rtwtypes.h" + + +/* ******************************************************************* + * Global Constants (VALUES MUST MATCH CONSTANTS IN SIMULINK PROVIDER) + ******************************************************************* */ +#define MAX_MSG_LEN 256 /* using #define because c90 has issues with variable arrays */ + +/* shared memory for PublishPoints */ +static const unsigned int NUM_PUBLISH_POINTS_SHM_KEY = 10610; +static const unsigned int PUBLISH_POINTS_SHM_KEY = 10613; +/* For real-time code execution in step() function */ +static const unsigned int TIME_SHM_KEY = 10623; + +/* fifo queue for UpdatePonts messages */ +static const char* UPDATES_FIFO = "/tmp/updates_fifo"; + +/* provider <-> solver sync */ +static const char* PUBLISH_SEM = "publish_sem"; +static const char* UPDATES_SEM = "updates_sem"; + + +/* ******************************** + * Global Internal Helper Variables + ******************************** */ +/* solver input/output files */ +static const char* PUBLISH_POINTS_FILE = "publishPoints.txt"; +static const char* GROUND_TRUTH_FILE = "groundTruth.txt"; +static const char* DEBUG_FILE = "debug.on"; +static const char* DEBUG_VERBOSE_FILE = "debug.verbose"; +static const char* GROUND_TRUTH_FILTER = "Input-Output Signals"; +static const unsigned int GT_PRINT_INTERVAL = 9000; /* no real measurement */ + +/* 1 = debug printing enabled, 0 = no debug output */ +static bool DEBUG = 0; +static bool DEBUG_VERBOSE = 0; +static const unsigned int DEBUG_PRINT_INTERVAL = 1; + +static const unsigned int EXIT_ERROR = 1; + + +/* ******************************** + * Simulink Model Mapping Variables + ******************************** */ +const rtwCAPI_ModelMappingInfo* modelMap; +const rtwCAPI_DataTypeMap* dataTypeMap; +const rtwCAPI_DimensionMap* dimensionMap; +const rtwCAPI_FixPtMap* fixedPointMap; +const uint_T* dimensionArray; +void** dataAddressMap; + + +/* ******************************* + * PublishPoints Related Variables + ******************************* */ +/* from file */ +unsigned int numPublishPoints; +char** publishPoints; + +/* from model */ +unsigned int numModelSignals; +const rtwCAPI_Signals* modelSignals; + +/* shared memory */ +int numPublishPointsShmId; +char* numPublishPointsShmAddress; +unsigned int publishPointsShmSize; +int publishPointsShmId; +char* publishPointsShmAddress; + +/* provider <-> solver sync */ +sem_t* publishSemaphore; + + +/* ****************************** + * UpdatePoints Related Variables + ****************************** */ +/* from model */ +unsigned int numModelParameters; +const rtwCAPI_BlockParameters* modelParameters; + +/* provider <-> solver sync */ +sem_t* updatesSemaphore; +int updatesFifo; + +/* ******************************* + * TimeShm Related Variables + ******************************* */ +int timeShmId; +char* timeShmAddress; +char* t_old; +char *eptr; +struct timespec start, finish, initial; +double currentTime; +double t_oldSim; +double t_oldStart; +double wait; +double jitter; +double ns2ms; +double s2ms; +double internalExeTime; +double externalExeTime; +double majorTimeStep; +double expectedInterval; +bool isIntegral; +bool isInterval; + +/* ********************************** + * Model Hooks (called from Simulink) + ********************************** */ +void init(rtwCAPI_ModelMappingInfo*); +void step(time_T*); +void term(); + + +/* ************************* + * Model Interaction Helpers + ************************* */ +void publishState(); +void applyUpdates(); +char* getModelSignalValue(const char*); +void updateModelParameterValue(const char*, char*); + + +/* ********************* + * Shared Memory Helpers + ********************* */ +int createSharedMemory(key_t, int); +char* attachSharedMemory(int); +void destroySharedMemory(key_t, char*); + + +/* ************* + * Misc. Helpers + ************* */ +void readFileToMemory(const char*, char***, unsigned int*); +void printGroundTruth(); +void printModelMemberCounts(); + +#endif /* MODEL_HOOKS */ diff --git a/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/publishPoints.txt b/src/bennu/executables/bennu-simulink-provider/solver/modelhooksRealtime/publishPoints.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/pybennu/pybennu/executables/pybennu_alicanto.py b/src/pybennu/pybennu/executables/pybennu_alicanto.py new file mode 100644 index 00000000..43682ec5 --- /dev/null +++ b/src/pybennu/pybennu/executables/pybennu_alicanto.py @@ -0,0 +1,283 @@ +"""Alicanto is a new feature made to be a more simple co-simulation tool than HELICS. + +The code is similar to the bennu HELICS code but stripped down. +Alicanto runs as a Subscriber and Client object. It takes in a configuration file (which points to a json) which defines which points Alicanto cares about +JSON format + - Subscriptions tell Alicanto which publish point (udp) to subscrie to and which point to keep track of + - Endpoints tell Alicanto where to corelate that subscribed point to a server-endpoint +""" +import logging +import threading +import argparse +import json +import signal +import sys +import time +import math + +from pybennu.distributed.subscriber import Subscriber +from pybennu.distributed.client import Client +import pybennu.distributed.swig._Endpoint as E + +logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger('alicanto') +#logger.addHandler(logging.StreamHandler()) + + +class alicantoSubscriber(Subscriber): + def __init__(self, sub_source): + new_publish_endpoint = E.new_Endpoint() + E.Endpoint_str_set(new_publish_endpoint, 'udp://'+str(sub_source)) + Subscriber.__init__(self, new_publish_endpoint) + +class alicantoClient(Client): + def __init__(self, end_dest): + new_endpoint_dest = E.new_Endpoint() + E.Endpoint_str_set(new_endpoint_dest, 'tcp://'+str(end_dest)) + self.endpointName = 'tcp://'+str(end_dest) + Client.__init__(self, new_endpoint_dest) + + def send(self, message): + """ Send message to Provider + """ + # send update + self._Client__socket.send_string(message+'\0') # must include null byte + # get response + msg = self._Client__socket.recv_string() + reply = msg.split('=') + status = reply[0] + data = reply[1] + + if status != self._Client__kACK: + logger.error(msg) + + return reply + +class alicanto(): + def __init__(self, config, debug=False, exit_handler=None): + + if debug: + logger.setLevel(logging.DEBUG) + else: + logger.setLevel(logging.INFO) + + exit = exit_handler if exit_handler else self.ctrl_exit_handler + signal.signal(signal.SIGINT, exit) # ctlr + c + + self.lock = threading.Lock() + + # Initialize system state + self.state = {} + # Tag=>destination map + self.dests = {} + # Tag=>type map + self.types = {} + # Set of all tags + self.tags = {} + + ############## Get counts from json ###################### + cfg = None + self.end_count = 0 + self.sub_count = 0 + self.pub_count = 0 + + with open(config, 'r') as file: + cfg = json.loads(file.read()) + try: + self.end_count = len(cfg["endpoints"]) + logger.info(f"Number of endpoints: {self.end_count}") + except: + logger.exception("No endpoints found or endpoints incorrectly formated, check configuration file") + try: + self.sub_count = len(cfg["subscriptions"]) + logger.info(f"Number of subscriptions: {self.sub_count}") + except: + logger.exception("No subscriptions found or subscriptions incorrectly formated, check configuration file") + + # Diagnostics to confirm JSON config correctly added the required + # endpoints and subscriptions + self.endid = {} + self.end_dests = [] + for i, endpoint in enumerate(cfg["endpoints"]): + self.endid[i] = endpoint + end_name = endpoint["name"] + end_destination = endpoint["destination"] + end_type = endpoint["type"] + logger.debug(f"Registered endpoint ---> end_name: {end_name} ---> end_destination: {end_destination}") + self.tags.update({end_destination : 0}) + self.end_dests.append(end_destination) + self.dests[end_name] = end_destination + self.types[end_name] = end_type + self.types[end_destination] = end_type + # make end_dests elements unique + self.end_dests = list(set(self.end_dests)) + + self.subid = {} + self.sub_sources = [] + for i in range(0, self.sub_count): + self.subid[i] = cfg["subscriptions"][i] + sub_name = self.subid[i]["key"] + sub_type = self.subid[i]["type"] + sub_source = sub_name.split('/')[0] + self.sub_sources.append(sub_source) + try: + sub_info = self.subid[i]["info"] # stores logic for interdependencies + except: + sub_info = None + logger.debug(f"Registered subscription ---> sub_name: {sub_name} ---> sub_type: {sub_type} ---> sub_info: {sub_info}") + #sub_name = sub_name.split('/')[1] if '/' in sub_name else sub_name + self.tags.update({sub_name : 0 }) + self.types[sub_name] = sub_type + if sub_info: + logger.debug(f"********** LOGIC **********") + for exp in sub_info.split(';'): + lhs, rhs = exp.split('=') + self.logic[lhs.strip()] = rhs.strip() + logger.debug(f'{exp.strip()}') + #make sub_sources elements unique + self.sub_sources = list(set(self.sub_sources)) + + for tag in self.tags: + self.state[tag] = False if self.get_type(tag) == 'bool' else 0 + + for sub_source in self.sub_sources: + logger.debug(f"Launching Subscriber Thread ---> subscription: udp://{sub_source}") + subber = alicantoSubscriber(sub_source) + subber.subscription_handler = self._subscription_handler + self.__sub_thread = threading.Thread(target=subber.run) + self.__sub_thread.setName(sub_source) + self.__sub_thread.daemon = True + self.__sub_thread.start() + + self.end_clients = {} + for end_dest in self.end_dests: + # Initialize bennu Client + end_dest = end_dest.split('/')[0] + self.end_clients[end_dest] = alicantoClient(end_dest) + for key in list(self.end_clients.keys()): + logger.debug(f"End_client: {key}") + + def run(self): + ############## Entering Execution Mode ############################## + logger.info("Entered alicanto execution mode") + + ########## Main co-simulation loop #################################### + while True: + self.publish_state() + for key, value in self.endid.items(): + full_end_name = value["name"] + end_name = (full_end_name.split('/')[1] + if '/' in full_end_name + else full_end_name) + full_end_dest = value["destination"] + end_dest = (full_end_dest.split('/')[0] + if '/' in full_end_dest + else full_end_dest) + end_dest_tag = (full_end_dest.split('/')[1] + if '/' in full_end_dest + else full_end_dest) + + # !!need to add something to handle binary points + if self.types[full_end_name] == 'float' or self.types[full_end_name] == 'double': + if not math.isclose(float(self.tag(full_end_name)), float(self.tag(full_end_dest))): + self.end_clients[end_dest].write_analog_point(end_dest_tag, self.tag(full_end_name)) + reply = self.end_clients[end_dest].send("READ="+end_name) + value = reply[1].rstrip('\x00') + self.tag(full_end_dest, value) + elif self.types[full_end_name] == 'bool': + if str(self.tag(full_end_name)).lower() != str(self.tag(full_end_dest)).lower(): + self.end_clients[end_dest].write_digital_point(end_dest_tag, self.tag(full_end_name)) + reply = self.end_clients[end_dest].send("READ="+end_name) + value = reply[1].rstrip('\x00') + self.tag(full_end_dest, value) + + def publish_state(self): + logger.debug("=================== DATA ===================") + for tag in self.tags: + logger.debug(f"{tag:<30} --- {self.tag(tag):}") + logger.debug("============================================") + + def get_type(self, tag): + return self.types[tag] + + def tag(self, tag, value=None): + with self.lock: + if value is not None: + self.state[tag] = value + else: + if tag in self.state: + return self.state[tag] + else: + return False if self.get_type(tag) == 'bool' else 0 + + def _subscription_handler(self, message): + """Receive Subscription message + This method gets called by the Subscriber base class in its run() + method when a message from a publisher is received. + + Ex message: "point1.value:10.50,point2.value:1.00," + + Args: + message (str): published zmq message as a string + """ + points = message.split(',') + sub_source = threading.current_thread().name + + for point in points: + if not point: + continue + if point == "": + continue + + tag = point.split(':')[0] + full_tag = sub_source + '/' + tag + value = point.split(':')[1] + + if full_tag not in self.tags: + continue + + if value.lower() == 'false': + value = False + field = 'status' + elif value.lower() == 'true': + value = True + field = 'status' + else: + value = float(value) + field = 'value' + + if field == 'value': + if not math.isclose(float(self.tag(full_tag)), value): + self.tag(full_tag, value) + logger.info("UPDATE NOW: "+full_tag) + logger.info("New value: "+str(value)) + else: + continue + elif field == 'status': + logger.info("Cannot handle binary points") + continue + else: + continue + + def ctrl_exit_handler(self, signal, frame): + logger.info("SIGINT or CTRL-C detected. Exiting gracefully") + sys.exit() + +def main(): + + parser = argparse.ArgumentParser(description="SCEPTRE Power Solver Service") + parser.add_argument('-c', '--config-file', dest='config_file',default="/root/repos/alicanto.json", + help='Config file to load (.json)') + parser.add_argument('-d', '--debug', dest='debug',default=True, + help='') + + args = parser.parse_args() + + solver = alicanto(args.config_file,debug=args.debug) + solver.run() + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + sys.exit() \ No newline at end of file diff --git a/src/pybennu/setup.py b/src/pybennu/setup.py index 25c12203..bf0d14b0 100644 --- a/src/pybennu/setup.py +++ b/src/pybennu/setup.py @@ -138,6 +138,7 @@ def run(self): 'pybennu-test-ep-server-helics = pybennu.executables.pybennu_test_ep_server_helics:main', 'pybennu-test-subscriber = pybennu.executables.pybennu_test_subscriber:main', 'pybennu-probe = pybennu.executables.pybennu_probe:main', + 'pybennu-alicanto = pybennu.executables.pybennu_alicanto:main', 'pybennu-siren = pybennu.siren.siren:main' ] }