From 4ba2aa920486d8fc2b7402a1f89ff770068f5738 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:32:55 +0100 Subject: [PATCH 01/19] remove unneeded files, update dockerfile and install script --- CMakeLists.txt | 42 +----------------------- Dockerfile | 44 ++++++++++++++++++------- ecui-llserver.service | 15 --------- img/llserver.pdf | Bin 14486 -> 0 bytes img/llserver.png | Bin 30174 -> 0 bytes img/{stAscii.txt => logo_short.txt} | 0 img/txvAscii.txt | 49 ---------------------------- img/txvLogo.txt | 37 --------------------- img/txvLogoSquashed.txt | 29 ---------------- influxCsvExport/events.csv | 0 influxCsvExport/main | Bin 40208 -> 0 bytes install.sh | 23 +++++++------ scripts/plot.sh | 11 ------- scripts/pressure_v1.8.plt | 18 ---------- scripts/pressure_v1.9.plt | 16 --------- scripts/temp_v1.8.plt | 17 ---------- scripts/temp_v1.9.plt | 15 --------- scripts/thrust_v1.8.plt | 24 -------------- scripts/thrust_v1.9.plt | 22 ------------- src/LLController.cpp | 2 +- 20 files changed, 45 insertions(+), 319 deletions(-) delete mode 100644 ecui-llserver.service delete mode 100644 img/llserver.pdf delete mode 100644 img/llserver.png rename img/{stAscii.txt => logo_short.txt} (100%) delete mode 100644 img/txvAscii.txt delete mode 100644 img/txvLogo.txt delete mode 100644 img/txvLogoSquashed.txt delete mode 100644 influxCsvExport/events.csv delete mode 100755 influxCsvExport/main delete mode 100755 scripts/plot.sh delete mode 100644 scripts/pressure_v1.8.plt delete mode 100644 scripts/pressure_v1.9.plt delete mode 100644 scripts/temp_v1.8.plt delete mode 100644 scripts/temp_v1.9.plt delete mode 100644 scripts/thrust_v1.8.plt delete mode 100644 scripts/thrust_v1.9.plt diff --git a/CMakeLists.txt b/CMakeLists.txt index 813fb571..32ce4839 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,44 +47,4 @@ if(LINUX) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) -endif() - - -##----WITH BOOST -# cmake_minimum_required(VERSION 3.13) -# project(llserver_ecui_houbolt) - -# find_package(Threads REQUIRED) - -# set(CMAKE_CXX_STANDARD 17) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") - -# #if(NOT TARGET spdlog) -# # # Stand-alone build -# # find_package(spdlog REQUIRED) -# #endif() - -# include_directories(include) - -# file(GLOB sources ./*.cpp src/*.cpp include/*.h) - -# set(Boost_USE_STATIC_LIBS OFF) -# set(Boost_USE_MULTITHREADED ON) -# set(Boost_USE_STATIC_RUNTIME OFF) -# find_package(Boost 1.45.0 COMPONENTS system) - -# if(Boost_FOUND) -# add_executable(${PROJECT_NAME} ${sources}) -# #spdlog_enable_warnings(${PROJECT_NAME}) - - -# if(UNIX AND NOT APPLE) -# set(LINUX TRUE) -# endif() - -# if(LINUX) -# target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads -lstdc++fs Boost::system)#spdlog::spdlog) -# else() -# target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads Boost::system)#spdlog::spdlog) -# endif() -# endif() +endif() \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8587575a..5c6ad145 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,37 @@ -FROM ubuntu:20.04 +# specify the node base image with your desired version node: +FROM ubuntu -# Replace shell with bash so we can source files -RUN rm /bin/sh && ln -s /bin/bash /bin/sh -# Set debconf to run non-interactively -RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections +WORKDIR /home -# Install base dependencies -RUN apt-get update && apt-get install -y -q --no-install-recommends \ - git \ - cmake \ - build-essential -WORKDIR /llserver_ecui_houbolt +### KVASER Driver -CMD ["bash"] +RUN apt-get update +RUN apt-get install -y build-essential +RUN apt-get install -y linux-headers-`uname -r` +RUN apt-get install -y cmake make +RUN apt-get install -y wget + +RUN wget --content-disposition "https://www.kvaser.com/downloads-kvaser/?utm_source=software&utm_ean=7330130980754&utm_status=latest" +RUN tar xvzf linuxcan.tar.gz +WORKDIR /home/linuxcan/canlib +RUN make +RUN make install +RUN /usr/doc/canlib/examples/listChannels + +WORKDIR /home/ + +# Clone the conf files into the docker container +ADD ./ /home/llserver_ecui_houbolt +WORKDIR /home/llserver_ecui_houbolt + +RUN mkdir -p build +WORKDIR /home/llserver_ecui_houbolt/build + +RUN cmake -D NO_PYTHON=true -S ../ -B ./ +RUN make -j + +ENV ECUI_CONFIG_PATH=/home/config_ecui + +ENTRYPOINT ./llserver_ecui_houbolt \ No newline at end of file diff --git a/ecui-llserver.service b/ecui-llserver.service deleted file mode 100644 index 7ea5b0d4..00000000 --- a/ecui-llserver.service +++ /dev/null @@ -1,15 +0,0 @@ -Description=Service for ECUI Low Level Server - -Wants=network.target -After=syslog.target network-online.target - -[Service] -Type=simple -ExecStart=/home/pi/llserver_ecui_houbolt/llserver_ecui_houbolt -WorkingDirectory=/home/pi/llserver_ecui_houbolt/ -Restart=always -RestartSec=10 -KillMode=process - -[Install] -WantedBy=multi-user.target diff --git a/img/llserver.pdf b/img/llserver.pdf deleted file mode 100644 index d05b87919948c739723250537ee798242112f1ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14486 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

4;mhfSp^9CM2$&VbTc9>f&qQ)reR|eixyIu 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 diff --git a/img/llserver.png b/img/llserver.png deleted file mode 100644 index ffe6ce545ef3a1232e2897669e9c09b2efcd41f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30174 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 z 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 Date: Tue, 17 Jan 2023 13:36:28 +0100 Subject: [PATCH 02/19] update update script --- install.sh | 2 +- update.sh | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/install.sh b/install.sh index d97b5d77..83b0dd56 100644 --- a/install.sh +++ b/install.sh @@ -13,4 +13,4 @@ sudo docker run \ -v /dev:/dev \ -v /lib/modules:/lib/modules \ -e "ECUI_CONFIG_PATH=/home/config_ecui" \ - -it --name llserver-ecui llserver_ecui + --rm -it --name llserver-ecui llserver_ecui diff --git a/update.sh b/update.sh index 887bbc5e..d525f4f7 100755 --- a/update.sh +++ b/update.sh @@ -4,8 +4,4 @@ cd "$(dirname "$0")" git pull -cmake . -DCMAKE_BUILD_TYPE:STRING=Debug -DNO_CANLIB:BOOL=True -DNO_PYTHON:BOOL=True - -make -j3 - -sudo systemctl restart ecui-llserver.service +exec ./install.sh From 9d067aea00624b61a316d965a856ee45d8786963 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:40:39 +0100 Subject: [PATCH 03/19] add ci.yml --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..cbe2446b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: Docker Image CI + +on: + push: + branches: [ "main", "dev" ] + pull_request: + branches: [ "main", "dev" ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Build the Docker image + run: docker build -f Dockerfile -t llserver_ecui.:$(date +%s) \ No newline at end of file From 66b9602abf5192ebfa557a96f44545d79ce9bdff Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:42:36 +0100 Subject: [PATCH 04/19] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbe2446b..da627568 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,4 +15,4 @@ jobs: steps: - uses: actions/checkout@v3 - name: Build the Docker image - run: docker build -f Dockerfile -t llserver_ecui.:$(date +%s) \ No newline at end of file + run: docker build -f Dockerfile -t llserver_ecui:$(date +%s) From 39ca44e5b588868682a12a26b7c49f189ad76458 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:43:52 +0100 Subject: [PATCH 05/19] fix ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da627568..375a1af1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,4 +15,4 @@ jobs: steps: - uses: actions/checkout@v3 - name: Build the Docker image - run: docker build -f Dockerfile -t llserver_ecui:$(date +%s) + run: docker build -f Dockerfile -t llserver_ecui:$(date +%s) . From ea8338585928558aca46959a53c61a7119093101 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:47:53 +0100 Subject: [PATCH 06/19] add submodule init to dockerfile --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5c6ad145..0ffa6f66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,6 +26,9 @@ WORKDIR /home/ ADD ./ /home/llserver_ecui_houbolt WORKDIR /home/llserver_ecui_houbolt +RUN git submodule init +RUN git submodule update + RUN mkdir -p build WORKDIR /home/llserver_ecui_houbolt/build From 2e13f9c6356d0a07dc37c70c4dd757eb9a9a135f Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 13:54:35 +0100 Subject: [PATCH 07/19] add git to dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 0ffa6f66..2af58395 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /home ### KVASER Driver RUN apt-get update +RUN apt-get install -y git RUN apt-get install -y build-essential RUN apt-get install -y linux-headers-`uname -r` RUN apt-get install -y cmake make From 6c7ffc3a703a723fe11f2d87c21f273152de0f0e Mon Sep 17 00:00:00 2001 From: markuspinter Date: Tue, 17 Jan 2023 14:00:34 +0100 Subject: [PATCH 08/19] update ci.yml --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 375a1af1..8d5550d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Docker Image CI on: push: - branches: [ "main", "dev" ] + branches: [ "main" ] pull_request: - branches: [ "main", "dev" ] + branches: [ "main" ] jobs: From e1fda401ddbfd4e66ff40185dcd7033a7f8fe1bc Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sat, 21 Jan 2023 01:12:26 +0100 Subject: [PATCH 09/19] start to write documentation --- README.md | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++-- install.sh | 3 + 2 files changed, 235 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e2b4ed3e..d9ba8187 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,243 @@ # Low Level Server for the Engine Control User Interface -## Links +## Table of Contents -Documentation of whole ECUI and Setup Guide -### [TXV_ECUI_WEB](https://github.com/SpaceTeam/TXV_ECUI_WEB/tree/dev) +- [Low Level Server for the Engine Control User Interface](#low-level-server-for-the-engine-control-user-interface) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Requirements](#requirements) + - [Low Level Server](#low-level-server) + - [The importance of States](#the-importance-of-states) + - [Events](#events) + - [State to CAN Command](#state-to-can-command) + - [State to State](#state-to-state) + - [Trigger Types](#trigger-types) + - [CAN Protocol](#can-protocol) + - [Configuration](#configuration) + - [config.json](#configjson) + - [mapping.json](#mappingjson) + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) + - [Troubleshooting](#troubleshooting) -Temperature Sensors over Ethernet with Siliconsystems TMP01 +## Overview -### [TXV_ECUI_TMPoE](https://github.com/SpaceTeam/TXV_ECUI_TMPoE/tree/master) +The Engine Control User Interface (ECUI) Suite consists of multiple programms that +form a system for Monitoring and Remote Control Purposes. Historically it was developed +for Testing Rocket Engines and has then been further extended to be usable as a MissionControl +Interface. For further information follow [SpaceTeam Mission Control System](...) -![LLServer Diagram](img/llserver.png) +## Requirements -# Notes: +>Note: If you would like to use the llserver out of the box you also need to +install our webserver and a bunch of other tools. But don't worry, **we've written +an easy to setup guide in our [config_ecui](https://github.com/SpaceTeam/config_ecui) +repository.** Otherwise you first need to implement a tcp server with our communication +protocol in order to start receiving data. + +The simplest way to use the llserver is by setting it up inside a docker container +in that case docker engine needs to be preinstalled. + +First you need to install an influxdb (docker container recommended) + +Then you can run +``` +sudo chmod +x install.sh +./install.sh +``` + +This script generates and mounts a config folder in the parent directory named +config_ecui where the [config.json](#configjson) and [mapping.json](#mappingjson) files must be present. + +## Low Level Server + +**The Low Level Server is responsible for handling and processing all time critical tasks** + +This includes the implementation of +- our own [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +- the Database Interface (influxDB) +- the Control Sequence Logic +- the Interface to our own [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) + +The complexity of this program lies in the various configurable +options for initialization and during runtime. They are split into two files that +**must** must reside in the same directory: + +- config.json - the config file for socket endpoints, sampling rates, CAN bus params, etc. +- mapping.json + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) + +In our setup these files can be found in [config_ecui](https://github.com/SpaceTeam/config_ecui), but this is not mandatory. +The config file directory path can be set either by passing it on start as an argument +or by setting the environment variable **ECUI_CONFIG_PATH**. The argument has +priority over the environment variable. + +## The importance of States +The llserver manages a hashmap filled with states which is called the +**State Controller**. Basically every variable inside the system is represented +as a double value inside the state controller. This includes user inputs. + +Each entry consists of a + +- state name - as key +- value - as double +- timestamp - 0 when uninitialized else timestamp of the last change +- dirty flag - used for transmitting periodic status updates to the web server + +We follow a naming convention which mostly boils down to + +`prefix:channel_name:state` + +as the `prefix` it is often used `gui` as all user inputs are also tracked in the +state controller and logged to the database. In case if something goes wrong we can +analyze whether a wrong user input was made or another event caused the trouble. +**User inputs must be prefixed with `gui` in order to be processed correctly.** + +Despite our naming convetion of state names, one can input a state name +with as many `":"` dividers as desired. + +>WARNING: Although a `:` is used to make the state names more readable and +processable it shall be pointed out that this notation is not supported in +html. Hence all `:` are translated to `-` when received at the web client. + +## Events +Another key feature of the llserver is the event system. An event can be triggered +when a specific state changes its value. This is used for translating user inputs +to CAN commands that are transmitted over the CAN bus for example. But it is also +possible to trigger an event based on a sensor value of a specific node (since they +are all represented as states). For this behaviour to work there are two types of events + +### State to CAN Command + +``` +"gui:Flash:Clear": [ + { + "command": "ecu:RequestFlashClear", + "parameters": [] + }, + { + "command": "pmu:RequestFlashClear", + "parameters": [] + }, + { + "command": "rcu:RequestFlashClear", + "parameters": [] + } +], +``` +In this example the `gui:Flash:Clear` state indicates a button press on the user interface. When it is pressed, a request to clear the flash of each electronics board +(ecu, pmu, rcu) shall be transmitted. The `parameters` entry allows for additional +arguments that may be required depending on the specified command. When +a string is located inside the parameters list, the llserver tries to resolve it +as a state inside the state controller and use its value instead. This is especially +useful for analog user inputs. + +### State to State +``` +"ecu:FlashStatus": [ + { + "state": "gui:Flash:Clear", + "triggerType": "!=", + "triggerValue": 0, + "value": 0 + } +], +``` + +In bot cases it is possible to trigger multiple actions in one go as seen in the +example of `gui:Flash:Clear`. Also in both cases it is possible to specify a triggerType. + +### Trigger Types +Each event entry can include a `triggerType` with an additional `triggerValue`. +This is needed if the event shall only +be triggered when certain conditions are met, not everytime the state gets changed. +Possible trigger types are: + +- `==` --> triggers when the state value equals the `triggerValue` +- `!=` --> triggers when the state value not equals the `triggerValue` +- `>=` --> triggers when the state value is greater or equal than the `triggerValue` +- `<=` --> triggers when the state value is smaller or equal than the `triggerValue` +- `>` --> triggers when the state value greater than the `triggerValue` +- `<` --> triggers when the state value smaller than the `triggerValue` + +## CAN Protocol +Please refer to the documentation in our [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) repository. +## Configuration + +### config.json + + + +### mapping.json + +The mapping.json file consists of four different parts. Each one must be at least +declared as an empty object, i.e. + +``` +{ + "CANMapping": {}, + "DefaultEventMapping": {}, + "EventMapping": {}, + "GUIMapping": {} +} +``` +#### CANMapping + +>Example: +>``` +>"CANMapping": { +> "7": { +> "0": { +> "offset": 0, +> "slope": 0.00010071, +> "stringID": "pmu_5V_voltage" +> }, +> "1": { +> "offset": 0, +> "slope": 0.00010071, +> "stringID": "pmu_5V_high_load_voltage" +> }, +> "2": { +> "offset": 0, +> "slope": 0.00033234, +> "stringID": "pmu_12V_voltage" +> } +> "stringID": "pmu" +> } +> }, +>``` +Since in a CAN Network multiple **Nodes** comunicate with each other using IDs, +we want to assign readable names to each node and each channel, so we +can work with them more easily. +A node entry starts with its unique identifier as a key and contains an object, +with arbitrary many channel entries. Each channel has a sensor state that +can be scaled with the two entries `offset` and `slope`. The readable name +is defined with `stringID`. +A specification of the channel type is not needed since this information is loaded +when the nodes get initialized. + + +#### DefaultEventMapping +#### EventMapping +#### GUIMapping + +## Troubleshooting - Every Sequence needs to define each device at the "START" timestamp - **Make sure every "sensorsNominalRange" object in the sequence contains ALL sensors, with a range -specified** \ No newline at end of file +specified** + + +- if the llserver fails to connect to the web server or carshes instantly try +to check if the correct ip addresses are used in the config file with +`sudo docker network inspect bridge` + +- if either or both web and llserver refuse to start, try and check the docker environment variable for the correct config path + +- if the llserver crashes instantly check if influx is correctly installed \ No newline at end of file diff --git a/install.sh b/install.sh index 83b0dd56..33b3045b 100644 --- a/install.sh +++ b/install.sh @@ -2,6 +2,9 @@ cd "$(dirname "$0")" +CONFIG_DIR="../config_ecui" + +mkdir -p $CONFIG_DIR #llserver ecui sudo DOCKER_BUILDKIT=0 docker build \ -t llserver_ecui -f Dockerfile . From 2023af9f3e9865445cd50b89fbf4d2c57c524745 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 22 Jan 2023 10:14:44 +0100 Subject: [PATCH 10/19] add mapping sections --- README.md | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d9ba8187..41816172 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ - [Overview](#overview) - [Requirements](#requirements) - [Low Level Server](#low-level-server) + - [CAN Protocol](#can-protocol) - [The importance of States](#the-importance-of-states) - [Events](#events) - [State to CAN Command](#state-to-can-command) - [State to State](#state-to-state) - [Trigger Types](#trigger-types) - - [CAN Protocol](#can-protocol) - [Configuration](#configuration) - [config.json](#configjson) - [mapping.json](#mappingjson) @@ -78,6 +78,12 @@ The config file directory path can be set either by passing it on start as an ar or by setting the environment variable **ECUI_CONFIG_PATH**. The argument has priority over the environment variable. +## CAN Protocol +As many terms from the [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +are also used to describe many functionalities +in the llserver, it is recommended to read through this documentation first before +you continue. + ## The importance of States The llserver manages a hashmap filled with states which is called the **State Controller**. Basically every variable inside the system is represented @@ -110,7 +116,8 @@ html. Hence all `:` are translated to `-` when received at the web client. Another key feature of the llserver is the event system. An event can be triggered when a specific state changes its value. This is used for translating user inputs to CAN commands that are transmitted over the CAN bus for example. But it is also -possible to trigger an event based on a sensor value of a specific node (since they +possible to trigger an event based on a sensor value or actuator state +of a specific hardware channel (since they are all represented as states). For this behaviour to work there are two types of events ### State to CAN Command @@ -129,7 +136,7 @@ are all represented as states). For this behaviour to work there are two types o "command": "rcu:RequestFlashClear", "parameters": [] } -], +] ``` In this example the `gui:Flash:Clear` state indicates a button press on the user interface. When it is pressed, a request to clear the flash of each electronics board (ecu, pmu, rcu) shall be transmitted. The `parameters` entry allows for additional @@ -147,7 +154,7 @@ useful for analog user inputs. "triggerValue": 0, "value": 0 } -], +] ``` In bot cases it is possible to trigger multiple actions in one go as seen in the @@ -166,13 +173,45 @@ Possible trigger types are: - `>` --> triggers when the state value greater than the `triggerValue` - `<` --> triggers when the state value smaller than the `triggerValue` -## CAN Protocol -Please refer to the documentation in our [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) repository. +>NOTE: An event with a triggerType is only triggered when the previous value +was outside the trigger range! In this way the event only gets triggered when +the value changes from outside the trigger range to the inside of the trigger range which +means that if the value remains inside the trigger range over a long period the event +only gets triggered once. + +Example: +``` +"gui:fuel_main_valve:checkbox": [ + { + "command": "fuel_main_valve:SetTargetPosition", + "triggerType": "==", + "triggerValue": 0, + "parameters": [0] + }, + { + "command": "fuel_main_valve:SetTargetPosition", + "triggerType": "!=", + "triggerValue": 0, + "parameters": [65535] + } +] +``` +In this case the gui element for the fuel main valve is represented as a checkbox. +when the checkbox gets pressed, the fuel main valve opens completely. Otherwise +the valve closes completely. When multiple actions per state exist, the program +processes them step by step, as defined in the json array. **So be reminded that +different ordering can cause different behaviours for more complex event mappings.** + +>NOTE: the fuel_main_valve gui button and hardware channel have no relation at the +beginning. Only an entry in the EventMapping links them together. + ## Configuration ### config.json - +In the config.json file all variables are defined that are important for the +initialization process of the llserver. A complete example config file with +all possible entries can be viewed in the [config_ecui](https://github.com/SpaceTeam/config_ecui) repo. ### mapping.json @@ -216,7 +255,7 @@ Since in a CAN Network multiple **Nodes** comunicate with each other using IDs, we want to assign readable names to each node and each channel, so we can work with them more easily. A node entry starts with its unique identifier as a key and contains an object, -with arbitrary many channel entries. Each channel has a sensor state that +with arbitrary many channel entries. Each channel has a `:sensor` state that can be scaled with the two entries `offset` and `slope`. The readable name is defined with `stringID`. A specification of the channel type is not needed since this information is loaded @@ -224,8 +263,79 @@ when the nodes get initialized. #### DefaultEventMapping + +The default event mapping can be used to define generic behaviour for gui elements +acting on hardware channels. +``` +"DefaultEventMapping": { + "DigitalOut": [ + { + "command": "DigitalOut:SetState", + "parameters": [ + "DigitalOut" + ] + } + ], + "Servo": [ + { + "command": "Servo:SetTargetPosition", + "parameters": [ + "Servo" + ] + } + ] +} +``` +The default behaviour for a gui element can be overwritten by an entry +in the event mapping. The key of an action may be an hardware channel type +which defines an action for each hardware channel of this type. The +channel type name can be also used to describe the command and parameters. +This gets then replaced with the actual channel name that is currently processed. + #### EventMapping +``` +"EventMapping": { + "gui:Flash:Clear": [ + { + "command": "ecu:RequestFlashClear", + "parameters": [] + }, + { + "command": "pmu:RequestFlashClear", + "parameters": [] + }, + { + "command": "rcu:RequestFlashClear", + "parameters": [] + } + ] +} +``` +An entry in the event mapping prevents default behaviours defined in the +DefaultEventMapping from being triggered. For further information about +events go to the [Events](#events) section. #### GUIMapping +``` +"GUIMapping": [ + { + "label": "Fuel Tank Pressure", + "state": "fuel_tank_pressure:sensor" + }, + { + "label": "Ox Tank Pressure", + "state": "ox_tank_pressure:sensor" + }, + { + "label": "Supercharge Valve", + "state": "supercharge_valve:sensor" + } +] +``` + +The GUI Mapping is a config option to tell the user interface, how a gui element +with a certain state shall be named in an even more human readable way as state names. +This information only gets transmitted to the user interface and has no implications +on the llserver. ## Troubleshooting From adcca06a952c42bf60d923eb404f5bb689756762 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 22 Jan 2023 10:53:54 +0100 Subject: [PATCH 11/19] update readme --- README.md | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 41816172..cf3a0929 100644 --- a/README.md +++ b/README.md @@ -228,29 +228,28 @@ declared as an empty object, i.e. ``` #### CANMapping ->Example: ->``` ->"CANMapping": { -> "7": { -> "0": { -> "offset": 0, -> "slope": 0.00010071, -> "stringID": "pmu_5V_voltage" -> }, -> "1": { -> "offset": 0, -> "slope": 0.00010071, -> "stringID": "pmu_5V_high_load_voltage" -> }, -> "2": { -> "offset": 0, -> "slope": 0.00033234, -> "stringID": "pmu_12V_voltage" -> } -> "stringID": "pmu" -> } -> }, ->``` +``` +"CANMapping": { + "7": { + "0": { + "offset": 0, + "slope": 0.00010071, + "stringID": "pmu_5V_voltage" + }, + "1": { + "offset": 0, + "slope": 0.00010071, + "stringID": "pmu_5V_high_load_voltage" + }, + "2": { + "offset": 0, + "slope": 0.00033234, + "stringID": "pmu_12V_voltage" + } + "stringID": "pmu" + } + }, +``` Since in a CAN Network multiple **Nodes** comunicate with each other using IDs, we want to assign readable names to each node and each channel, so we can work with them more easily. From c05cddd04a95be7abf33996575b8f3a619929d02 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 22 Jan 2023 20:10:00 +0100 Subject: [PATCH 12/19] update readme --- README.md | 246 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf3a0929..9aaadb7b 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,9 @@ - [Table of Contents](#table-of-contents) - [Overview](#overview) - [Requirements](#requirements) + - [Installation](#installation) + - [Build options](#build-options) + - [Supported CAN Drivers](#supported-can-drivers) - [Low Level Server](#low-level-server) - [CAN Protocol](#can-protocol) - [The importance of States](#the-importance-of-states) @@ -21,6 +24,11 @@ - [DefaultEventMapping](#defaulteventmapping) - [EventMapping](#eventmapping) - [GUIMapping](#guimapping) + - [Autonomous Control Test Sequence](#autonomous-control-test-sequence) + - [`globals` section](#globals-section) + - [`data` section](#data-section) + - [Abort Sequence Format](#abort-sequence-format) + - [TCP Socket Message Types](#tcp-socket-message-types) - [Troubleshooting](#troubleshooting) ## Overview @@ -43,7 +51,9 @@ in that case docker engine needs to be preinstalled. First you need to install an influxdb (docker container recommended) -Then you can run +## Installation + +To install the llserver using docker you can run ``` sudo chmod +x install.sh ./install.sh @@ -52,12 +62,36 @@ sudo chmod +x install.sh This script generates and mounts a config folder in the parent directory named config_ecui where the [config.json](#configjson) and [mapping.json](#mappingjson) files must be present. +### Build options +The project uses cmake for compiling. You can provide different build options to +cmake using the `-D` option: + +- `-D NO_PYTHON=[true | false]` +- `-D NO_CANLIB=[true | false]` + +Setting one of these build flags result in not binding the corresponding libraries +to avoid a compile error. If you would like to change one of these settings checkout +the Entrypoint line in the Dockerfile + +>NOTE: different build modes require different config variables, make sure you +have definded all of them properly. See [config_ecui](https://github.com/SpaceTeam/config_ecui). + +### Supported CAN Drivers +Currently two drivers are supported namely +- [Kvaser CANLIB](https://www.kvaser.com/canlib-webhelp/page_canlib.html) +- [SocketCAN](https://docs.kernel.org/networking/can.html) + +the preferred driver can be selected inside the [config.json](#configjson). +The Dockerfile also installs all required kvaser canlib library files automatically. + ## Low Level Server **The Low Level Server is responsible for handling and processing all time critical tasks** This includes the implementation of - our own [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +- a possible UDP endpoint for using our CAN Protocol in an optimized way using + our custom LoRa shield with a custom LoRa Driver (will get public soon) - the Database Interface (influxDB) - the Control Sequence Logic - the Interface to our own [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) @@ -84,6 +118,10 @@ are also used to describe many functionalities in the llserver, it is recommended to read through this documentation first before you continue. +The llserver keeps a hashmap of all commands that are supported of the connected +channels. These are used in the event mapping and control sequences to +send commands to the hardware. + ## The importance of States The llserver manages a hashmap filled with states which is called the **State Controller**. Basically every variable inside the system is represented @@ -336,6 +374,212 @@ with a certain state shall be named in an even more human readable way as state This information only gets transmitted to the user interface and has no implications on the llserver. +## Autonomous Control Test Sequence + +To be able to test a liquid engine it is normally required to execute multiple commands +in a very short amount of time (pressurization, ignition, engine ramp-up, etc.). + +For this purpose a sequence manager has been developed for automatic test sequences. + +Test Sequences are defined inside the `$ECUI_CONFIG_PATH/sequences/` folder. +Each sequence uses the JSON format and consists of two main objects: +- `globals` - general definitions used for the sequence processing +- `data` - the actual sequence + +### `globals` section +Inside the `globals` section following entries are needed + +| key | type | value | +| ----------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- | +| `"startTime"` | float | relative start of the squence (may also be negative) | +| `"endTime"` | float | relative end of the sequence | +| `"interpolation"` | object | object with command name as key and either `"none"`(step function behaviour) or `"linear"`(linear interpolation) as values | +| `"interval"` | float | time precision of each iteration step | +| `"ranges"` | array | list of state names used for range checking | + +The interpolation entry specifies the behaviour between datapoints of the sequence. +The range entry is used to specify each state used for range checking. This means that at each datapoint a range can be +set in which the specified sensor must reside. If the sensor value gets out of range and **autoabort** is active inside the config.json +the sequence gets aborted instantly. + +### `data` section + +The data section includes a list of **command group**. These can be +used to group multiple datapoints relatively to one timestamp in order +to be moved more easily along the time axis if for example the burntime changes. + +Each group command includes + | key | type | value | + | ------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------- | + | `"timestamp"` | string or float | if timestamp is string only `"START"` or `"END"` are allowed which get replace by `"startTime"` and `"endTime"` respectively. | + | `"name"` | string | name of the command group | + | `"desc"` | string | description | + | `"actions"` | array | list of datapoints with relative timestamp to command group | + | | + + +Each datapoint may include + | key | type | value | + | ---------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- | + | `"timestamp"` | float | timestamp relative to command group (here **only floats are valid!**) | + | `""` | array of floats | parameters for the specific command | + | `"sensorNominalRange"` | object | states as keys and array of two elements with range as values (must be defined in the global section `"ranges"`) | + + +The first datapoint of the first command group **HAS TO** include all commands used in the sequence for proper initialization! +Each number in actions except the timestamp is double on the LLServer. + +> Note: The keywords "START" or "END" are only allowed in the Group Commands (objects inside data array). +> +Example. +``` +{ + "globals": + { + "endTime": 15, + "interpolation": + { + "fuel_main_valve:SetTargetPosition": "linear", + "ox_main_valve:SetTargetPosition": "linear", + "igniter:SetState": "none" + }, + "interval": 0.01, + "ranges": + [ + "chamberPressure:sensor" + ], + "startTime": -10 + }, + "data": + [ + { + "timestamp": "START", + "name": "start", + "desc": "set all to initial state", + "actions": + [ + { + "timestamp": 0.0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0], + "igniter:SetState": [0], + "sensorsNominalRange": + { + "chamberPressure:sensor": [-5, 20] + } + } + ] + }, + { + "timestamp": -3.0, + "name": "ignition", + "desc": "lets light this candle", + "actions": + [ + { + "timestamp": 0, + "igniter:SetState": [65000] + } + ] + }, + { + "timestamp": 0.0, + "name": "engine startup", + "desc": "ramp up engine", + "actions": + [ + { + "timestamp": 0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + }, + { + "timestamp": 0.5, + "ox_main_valve:SetTargetPosition": [15000], + "fuel_main_valve:SetTargetPosition": [15000] + }, + { + "timestamp": 1.2, + "fuel_main_valve:SetTargetPosition": [25000], + "ox_main_valve:SetTargetPosition": [25000] + }, + { + "timestamp": 1.5, + "igniter:SetState": [0] + }, + { + "timestamp": 1.7, + "fuel_main_valve:SetTargetPosition": [65000], + "ox_main_valve:SetTargetPosition": [65000] + } + ] + }, + { + "timestamp": 10.0, + "name": "shutdown", + "desc": "engine shutdown", + "actions": + [ + { + "timestamp": 0, + "fuel_main_valve:SetTargetPosition": [65000], + "ox_main_valve:SetTargetPosition": [65000] + }, + { + "timestamp": 0.7, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + } + ] + }, + { + "timestamp": "END", + "name": "end", + "desc": "the end", + "actions": + [ + { + "timestamp": 0.0, + "fuel_main_valve:SetTargetPosition": [0], + "ox_main_valve:SetTargetPosition": [0] + } + ] + } + ] +} +``` + +### Abort Sequence Format + +Another feature is the ability to define a **"safe state"** to be +executed in order the test control sequence fails or gets aborted. + +Test Abort Sequence is defined inside the `$ECUI_CONFIG_PATH/sequences/abort_sequences/` folder. Currently only one abort sequence is supported and +must be named `AbortSequence.json`. + +For now exactly one object with all commands to be executed. +The `"endTime"` key in `"globals"` is used to describe for how long the logging should continue +in case of an abort. + + { + "globals": { + "endTime": 3.2 //double + }, + "actions" : { + "fuel_main_valve:SetTargetPosition": [0], //array[double] + "igniter:SetState": [0], //array[double] + "ox_main_valve:SetTargetPosition": [0] //array[double] + } + } + +### TCP Socket Message Types + +In order to communicate with the webserver, a tcp socket connection is +established. **It is mandatory to send the llserver specific messages +to initate state transmission and start test control sequences.** + +For the whole API documentation refer to [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) + ## Troubleshooting - Every Sequence needs to define each device at the "START" timestamp From 98fbe31ec5d2cd28eb3297336023ba47d10d97cb Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 22 Jan 2023 20:58:40 +0100 Subject: [PATCH 13/19] add udp lora driver to readme --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9aaadb7b..b59dfb13 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Low Level Server for the Engine Control User Interface +# Low Level Server for the SpaceTeam Mission Control System ## Table of Contents -- [Low Level Server for the Engine Control User Interface](#low-level-server-for-the-engine-control-user-interface) +- [Low Level Server for the SpaceTeam Mission Control System](#low-level-server-for-the-spaceteam-mission-control-system) - [Table of Contents](#table-of-contents) - [Overview](#overview) - [Requirements](#requirements) @@ -28,12 +28,14 @@ - [`globals` section](#globals-section) - [`data` section](#data-section) - [Abort Sequence Format](#abort-sequence-format) - - [TCP Socket Message Types](#tcp-socket-message-types) + - [TCP Socket Message Types](#tcp-socket-message-types) + - [UDP Socket Endpoint for LoRa](#udp-socket-endpoint-for-lora) + - [LoRa Config](#lora-config) - [Troubleshooting](#troubleshooting) ## Overview -The Engine Control User Interface (ECUI) Suite consists of multiple programms that +The SpaceTeam Mission Control System (STMC) Suite consists of multiple programms that form a system for Monitoring and Remote Control Purposes. Historically it was developed for Testing Rocket Engines and has then been further extended to be usable as a MissionControl Interface. For further information follow [SpaceTeam Mission Control System](...) @@ -238,7 +240,7 @@ In this case the gui element for the fuel main valve is represented as a checkbo when the checkbox gets pressed, the fuel main valve opens completely. Otherwise the valve closes completely. When multiple actions per state exist, the program processes them step by step, as defined in the json array. **So be reminded that -different ordering can cause different behaviours for more complex event mappings.** +a different order can cause different behaviours for more complex event mappings.** >NOTE: the fuel_main_valve gui button and hardware channel have no relation at the beginning. Only an entry in the EventMapping links them together. @@ -572,7 +574,7 @@ in case of an abort. } } -### TCP Socket Message Types +## TCP Socket Message Types In order to communicate with the webserver, a tcp socket connection is established. **It is mandatory to send the llserver specific messages @@ -580,6 +582,70 @@ to initate state transmission and start test control sequences.** For the whole API documentation refer to [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) +## UDP Socket Endpoint for LoRa + +This protocol is based on our [CAN Protocol](#can-protocol). + +This implementation uses LoRa as an unidirectional method for receiving +data from the rocket during flight. For receiving LoRa messages a custom +LoRa shield is used on a raspberry pi. (Repo coming soon) +A UDP socket connection is used for data transmission between llserver and +raspberry pi. + +After the internal control +of the rocket takes over and no further commands are sent to the rocket +the only message type that the rocket sends automatically and periodically +is the `DataMsg_t` of each Generic Channel. This means we can strip the LoRa +message down to only data messages and can even combine them into one large +message. This is done on our RCU (Radio Control Unit) which listens to the +CAN FD bus for any data messages and updates a buffer which is split into +as many regions as generic channels exists inside the rocket (number of CAN +nodes). + +Since the data message length is highly variable and considered to be +specified within the channel_mask, the header alone takes up an unnessesary +large amount of bytes that can be considered "constant" during +flight. Therefore the CAN header + data message header gets removed +for the LoRa messages +and is statically entered inside the [config.json](#configjson). + +There is only one additional byte for each generic channel which indicates if the +section contains valid data. **THIS BYTE IS NOT INCLUDED IN THE CONFIG canMsgSize ENTRY!** + +### LoRa Config + +The `LORA` section in the [config.json](#configjson) includes + +``` +"LORA": { + "ip": "192.168.1.7", + "port": 5001, + "nodeIDsRef": [6, 8], + "nodeIDs": [16, 18], + "canMsgSizes": [54, 39] +} +``` + +The `ip` and `port` describe the udp endpoint to the raspberry pi. + +The `nodeIDsRef` array defines the corresponding node ids on the CAN FD bus. This +is needed since the LoRa messages are injected in the CAN Manager as normal CAN messages +and therefore need to load the correct CAN header before the message is injected. This +means that the hardware **MUST** be connected over the CAN FD bus for the initialization +of the llserver in order to initialize the nodes correctly. Otherwise the LoRa messages cannot +be decoded and are lost! + +The `nodeIDs` array contains the node ids for the LoRa channels (in this case CAN FD bus node ids +prepended with 1) + +The `canMsgSizes` array contains the message size of each can data message (**HEADER BYTE EXCLUDED**) + + +>NOTE: that depending on the total message length, the LoRa driver on +the raspberrypi must be configured correctly as well. See (LoRa doc coming +soon...) + + ## Troubleshooting - Every Sequence needs to define each device at the "START" timestamp From c6c83db50484230ec8e85f850dc421266e69dfb8 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 22 Jan 2023 21:17:26 +0100 Subject: [PATCH 14/19] update readme --- README.md | 121 ++++++++++++++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index b59dfb13..82f6c5e8 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,34 @@ -# Low Level Server for the SpaceTeam Mission Control System - -## Table of Contents - - -- [Low Level Server for the SpaceTeam Mission Control System](#low-level-server-for-the-spaceteam-mission-control-system) - - [Table of Contents](#table-of-contents) - - [Overview](#overview) - - [Requirements](#requirements) - - [Installation](#installation) - - [Build options](#build-options) - - [Supported CAN Drivers](#supported-can-drivers) - - [Low Level Server](#low-level-server) - - [CAN Protocol](#can-protocol) - - [The importance of States](#the-importance-of-states) - - [Events](#events) - - [State to CAN Command](#state-to-can-command) - - [State to State](#state-to-state) - - [Trigger Types](#trigger-types) - - [Configuration](#configuration) - - [config.json](#configjson) - - [mapping.json](#mappingjson) - - [CANMapping](#canmapping) - - [DefaultEventMapping](#defaulteventmapping) - - [EventMapping](#eventmapping) - - [GUIMapping](#guimapping) - - [Autonomous Control Test Sequence](#autonomous-control-test-sequence) - - [`globals` section](#globals-section) - - [`data` section](#data-section) - - [Abort Sequence Format](#abort-sequence-format) - - [TCP Socket Message Types](#tcp-socket-message-types) - - [UDP Socket Endpoint for LoRa](#udp-socket-endpoint-for-lora) - - [LoRa Config](#lora-config) - - [Troubleshooting](#troubleshooting) +

Low Level Server for the SpaceTeam Mission Control System

+ +

Table of Contents

+ + +- [Overview](#overview) +- [Requirements](#requirements) +- [Installation](#installation) + - [Build options](#build-options) + - [Supported CAN Drivers](#supported-can-drivers) +- [CAN Protocol](#can-protocol) +- [The importance of States](#the-importance-of-states) +- [Events](#events) + - [State to CAN Command](#state-to-can-command) + - [State to State](#state-to-state) + - [Trigger Types](#trigger-types) +- [Configuration](#configuration) + - [config.json](#configjson) + - [mapping.json](#mappingjson) + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) +- [Autonomous Control Test Sequence](#autonomous-control-test-sequence) + - [`globals` section](#globals-section) + - [`data` section](#data-section) + - [Abort Sequence Format](#abort-sequence-format) +- [TCP Socket Message Types](#tcp-socket-message-types) +- [UDP Socket Endpoint for LoRa](#udp-socket-endpoint-for-lora) + - [LoRa Config](#lora-config) +- [Troubleshooting](#troubleshooting) ## Overview @@ -40,6 +37,32 @@ form a system for Monitoring and Remote Control Purposes. Historically it was de for Testing Rocket Engines and has then been further extended to be usable as a MissionControl Interface. For further information follow [SpaceTeam Mission Control System](...) +**The Low Level Server is responsible for handling and processing all time critical tasks** + +This includes the implementation of +- our own [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) +- a possible UDP endpoint for using our CAN Protocol in an optimized way using + our custom LoRa shield with a custom LoRa Driver (will get public soon) +- the Database Interface (influxDB) +- the Control Sequence Logic +- the Interface to our own [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) + +The complexity of this program lies in the various configurable +options for initialization and during runtime. They are split into two files that +**must** must reside in the same directory: + +- config.json - the config file for socket endpoints, sampling rates, CAN bus params, etc. +- mapping.json + - [CANMapping](#canmapping) + - [DefaultEventMapping](#defaulteventmapping) + - [EventMapping](#eventmapping) + - [GUIMapping](#guimapping) + +In our setup these files can be found in [config_ecui](https://github.com/SpaceTeam/config_ecui), but this is not mandatory. +The config file directory path can be set either by passing it on start as an argument +or by setting the environment variable **ECUI_CONFIG_PATH**. The argument has +priority over the environment variable. + ## Requirements >Note: If you would like to use the llserver out of the box you also need to @@ -86,34 +109,6 @@ Currently two drivers are supported namely the preferred driver can be selected inside the [config.json](#configjson). The Dockerfile also installs all required kvaser canlib library files automatically. -## Low Level Server - -**The Low Level Server is responsible for handling and processing all time critical tasks** - -This includes the implementation of -- our own [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) -- a possible UDP endpoint for using our CAN Protocol in an optimized way using - our custom LoRa shield with a custom LoRa Driver (will get public soon) -- the Database Interface (influxDB) -- the Control Sequence Logic -- the Interface to our own [Webserver](https://github.com/SpaceTeam/web_ecui_houbolt) - -The complexity of this program lies in the various configurable -options for initialization and during runtime. They are split into two files that -**must** must reside in the same directory: - -- config.json - the config file for socket endpoints, sampling rates, CAN bus params, etc. -- mapping.json - - [CANMapping](#canmapping) - - [DefaultEventMapping](#defaulteventmapping) - - [EventMapping](#eventmapping) - - [GUIMapping](#guimapping) - -In our setup these files can be found in [config_ecui](https://github.com/SpaceTeam/config_ecui), but this is not mandatory. -The config file directory path can be set either by passing it on start as an argument -or by setting the environment variable **ECUI_CONFIG_PATH**. The argument has -priority over the environment variable. - ## CAN Protocol As many terms from the [CAN Protocol](https://github.com/SpaceTeam/can_houbolt) are also used to describe many functionalities @@ -433,7 +428,7 @@ Each number in actions except the timestamp is double on the LLServer. > Note: The keywords "START" or "END" are only allowed in the Group Commands (objects inside data array). > -Example. +Example: ``` { "globals": From adda1aa17c08872baaee97a704b74e27697e1ead Mon Sep 17 00:00:00 2001 From: markuspinter Date: Mon, 23 Jan 2023 20:37:25 +0100 Subject: [PATCH 15/19] update dockerfile to install python as well --- CMakeLists.txt | 4 ++-- Dockerfile | 3 ++- src/drivers/PythonController.cpp | 10 ++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 32ce4839..1b4e22f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if(NO_PYTHON) add_compile_definitions(NO_PYTHON) list(REMOVE_ITEM sources ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/PythonController.cpp) else() - include_directories(/usr/include/python3.8) + include_directories(/usr/include/python3.10) endif() add_executable(${PROJECT_NAME} ${sources}) @@ -43,7 +43,7 @@ if(LINUX) target_link_libraries(${PROJECT_NAME} PRIVATE -lcanlib) endif() if(NOT NO_PYTHON) - target_link_libraries(${PROJECT_NAME} PRIVATE -lpython3.8) + target_link_libraries(${PROJECT_NAME} PRIVATE -lpython3.10) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) diff --git a/Dockerfile b/Dockerfile index 2af58395..f268bc89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN apt-get install -y build-essential RUN apt-get install -y linux-headers-`uname -r` RUN apt-get install -y cmake make RUN apt-get install -y wget +RUN apt-get install -y python3.10-dev RUN wget --content-disposition "https://www.kvaser.com/downloads-kvaser/?utm_source=software&utm_ean=7330130980754&utm_status=latest" RUN tar xvzf linuxcan.tar.gz @@ -33,7 +34,7 @@ RUN git submodule update RUN mkdir -p build WORKDIR /home/llserver_ecui_houbolt/build -RUN cmake -D NO_PYTHON=true -S ../ -B ./ +RUN cmake -D NO_PYTHON=false -D NO_CANLIB=false -S ../ -B ./ RUN make -j ENV ECUI_CONFIG_PATH=/home/config_ecui diff --git a/src/drivers/PythonController.cpp b/src/drivers/PythonController.cpp index 011e0123..a2b6954a 100644 --- a/src/drivers/PythonController.cpp +++ b/src/drivers/PythonController.cpp @@ -8,9 +8,9 @@ #include #include -static std::string PythonController::pyEnv = ""; +std::string PythonController::pyEnv = ""; -static void PythonController::SetPythonEnvironment(std::string pyEnv) { +void PythonController::SetPythonEnvironment(std::string pyEnv) { PythonController::pyEnv = pyEnv; } @@ -230,7 +230,8 @@ void PythonController::RunPyScript(std::string scriptPath) { PythonController::SetupImports(); - FILE *fp = _Py_fopen(scriptPath.c_str(), "r"); + std::wstring path = std::wstring(scriptPath.begin(), scriptPath.end()); + FILE *fp = _Py_wfopen(path.c_str(), L"r"); StateController::Instance() -> SetState((std::string) "python_running", 1, utils::getCurrentTimestamp()); @@ -270,7 +271,8 @@ void PythonController::RunPyScriptWithArgvWChar(std::string scriptPath, int pyAr PySys_SetArgv(pyArgc, pyArgv); - FILE *fp = _Py_fopen(scriptPath.c_str(), "r"); + std::wstring path = std::wstring(scriptPath.begin(), scriptPath.end()); + FILE *fp = _Py_wfopen(path.c_str(), L"r"); StateController::Instance() -> SetState((std::string) "python_running", 1, utils::getCurrentTimestamp()); From 84535e87d2ec063fc50bd860c18f4bcbca691e03 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 19 Mar 2023 17:47:45 +0100 Subject: [PATCH 16/19] change scaling for servos from 0 to 100 --- .vscode/launch.json | 4 ++-- src/can/Servo.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 41f8d155..1e900225 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,13 +3,13 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", - "configurations": [ + "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/llserver_ecui_houbolt", - "args": [], + "args": ["../config_ecui/"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/src/can/Servo.cpp b/src/can/Servo.cpp index c9d500fa..97b0e962 100644 --- a/src/can/Servo.cpp +++ b/src/can/Servo.cpp @@ -27,8 +27,8 @@ const std::vector Servo::states = const std::map> Servo::scalingMap = { - {"Position", {1.0, 0.0}}, - {"TargetPosition", {1.0, 0.0}}, + {"Position", {0.00152590219, 0.0}}, + {"TargetPosition", {0.00152590219, 0.0}}, {"TargetPressure", {1.0, 0.0}}, {"MaxSpeed", {1.0, 0.0}}, {"MaxAccel", {1.0, 0.0}}, From b7aad5dc62b34def3069e05ba3d96d8d82f89465 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Sun, 30 Apr 2023 18:48:03 +0200 Subject: [PATCH 17/19] uBetter kvaser bus params output --- .vscode/launch.json | 8 ++++---- README.md | 10 +++++----- src/can/CANDriverKvaser.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 1e900225..afa298fa 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,11 +8,11 @@ "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/llserver_ecui_houbolt", - "args": ["../config_ecui/"], + "program": "${workspaceFolder}/build/llserver_ecui_houbolt", + "args": [], "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], + "cwd": "${workspaceFolder}/build", + "environment": [{"name": "ECUI_CONFIG_PATH", "value": "../../config_ecui"}], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ diff --git a/README.md b/README.md index 82f6c5e8..ebfb0635 100644 --- a/README.md +++ b/README.md @@ -475,7 +475,7 @@ Example: [ { "timestamp": 0, - "igniter:SetState": [65000] + "igniter:SetState": [100] } ] }, @@ -506,8 +506,8 @@ Example: }, { "timestamp": 1.7, - "fuel_main_valve:SetTargetPosition": [65000], - "ox_main_valve:SetTargetPosition": [65000] + "fuel_main_valve:SetTargetPosition": [100], + "ox_main_valve:SetTargetPosition": [100] } ] }, @@ -519,8 +519,8 @@ Example: [ { "timestamp": 0, - "fuel_main_valve:SetTargetPosition": [65000], - "ox_main_valve:SetTargetPosition": [65000] + "fuel_main_valve:SetTargetPosition": [100], + "ox_main_valve:SetTargetPosition": [100] }, { "timestamp": 0.7, diff --git a/src/can/CANDriverKvaser.cpp b/src/can/CANDriverKvaser.cpp index 9a415be7..cb32feb7 100644 --- a/src/can/CANDriverKvaser.cpp +++ b/src/can/CANDriverKvaser.cpp @@ -274,7 +274,7 @@ canStatus CANDriverKvaser::InitializeCANChannel(uint32_t canBusChannelID) { return stat; } - Debug::print("can channel bus %d: arb-bitrate %d", canBusChannelID, arbitrationParamsMap[canBusChannelID].bitrate); + Debug::print("can channel bus %d: bitrate %d, arb-bitrate %d", canBusChannelID, dataParamsMap[canBusChannelID].bitrate, arbitrationParamsMap[canBusChannelID].bitrate); stat = canSetBusParams(canHandlesMap[canBusChannelID], arbitrationParamsMap[canBusChannelID].bitrate, arbitrationParamsMap[canBusChannelID].timeSegment1, From d5a965ff0550ffa64ce4b10073fda9e49bc23fc6 Mon Sep 17 00:00:00 2001 From: markuspinter Date: Thu, 8 Aug 2024 18:46:17 +0200 Subject: [PATCH 18/19] feat(PI-control): Add PI pressure control channel --- .vscode/settings.json | 3 +- CMakeLists.txt | 2 +- include/can/Channel.h | 2 +- include/can/PIControl.h | 71 +++++++ include/can_houbolt | 2 +- src/SequenceManager.cpp | 3 +- src/can/Control.cpp | 119 ++++++----- src/can/Node.cpp | 4 + src/can/PIControl.cpp | 453 ++++++++++++++++++++++++++++++++++++++++ src/can/Servo.cpp | 192 +++++++++-------- 10 files changed, 689 insertions(+), 162 deletions(-) create mode 100644 include/can/PIControl.h create mode 100644 src/can/PIControl.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index f95ef421..1ba192f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -88,5 +88,6 @@ "numbers": "cpp", "semaphore": "cpp", "stop_token": "cpp" - } + }, + "C_Cpp.errorSquiggles": "disabled" } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b4e22f7..3eb78b41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,4 +47,4 @@ if(LINUX) endif() else() target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) -endif() \ No newline at end of file +endif() diff --git a/include/can/Channel.h b/include/can/Channel.h index f193c63b..98727748 100644 --- a/include/can/Channel.h +++ b/include/can/Channel.h @@ -56,7 +56,7 @@ class Channel double result = value; if (a != 1 || b != 0) { - result = value / a + b; + result = (value - b) / a; } return result; }; diff --git a/include/can/PIControl.h b/include/can/PIControl.h new file mode 100644 index 00000000..9a11f68e --- /dev/null +++ b/include/can/PIControl.h @@ -0,0 +1,71 @@ +// +// Created by Markus on 03.09.21. +// + +#ifndef LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H +#define LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H + +#include "can/Channel.h" +#include "can/Node.h" +#include "can_houbolt/channels/pi_control_channel_def.h" + +class PIControl : public Channel, public NonNodeChannel +{ +public: + //TODO: MP check if this is the only and correct way to implement static const with inheritation + static const std::vector states; + static const std::map> scalingMap; + static const std::map variableMap; + + //-------------------------------RECEIVE Functions-------------------------------// + +public: + PIControl(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent); + + std::vector GetStates() override; + + //-------------------------------RECEIVE Functions-------------------------------// + + void ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, uint64_t ×tamp) override; + + //-------------------------------SEND Functions-------------------------------// + + void SetEnabled(std::vector ¶ms, bool testOnly); + void GetEnabled(std::vector ¶ms, bool testOnly); + + void SetTarget(std::vector ¶ms, bool testOnly); + void GetTarget(std::vector ¶ms, bool testOnly); + + void SetP(std::vector ¶ms, bool testOnly); + void GetP(std::vector ¶ms, bool testOnly); + + void SetI(std::vector ¶ms, bool testOnly); + void GetI(std::vector ¶ms, bool testOnly); + + void SetSensorSlope(std::vector ¶ms, bool testOnly); + void GetSensorSlope(std::vector ¶ms, bool testOnly); + + void SetSensorOffset(std::vector ¶ms, bool testOnly); + void GetSensorOffset(std::vector ¶ms, bool testOnly); + + void SetOperatingPoint(std::vector ¶ms, bool testOnly); + void GetOperatingPoint(std::vector ¶ms, bool testOnly); + + void SetActuatorChannelID(std::vector ¶ms, bool testOnly); + void GetActuatorChannelID(std::vector ¶ms, bool testOnly); + + void SetSensorChannelID(std::vector ¶ms, bool testOnly); + void GetSensorChannelID(std::vector ¶ms, bool testOnly); + + void SetRefreshDivider(std::vector ¶ms, bool testOnly); + void GetRefreshDivider(std::vector ¶ms, bool testOnly); + + void RequestStatus(std::vector ¶ms, bool testOnly) override; + void RequestResetSettings(std::vector ¶ms, bool testOnly) override; + + //-------------------------------Utility Functions-------------------------------// + + void RequestCurrentState() override; +}; + +#endif //LLSERVER_ECUI_HOUBOLT_PI_CONTROL_H diff --git a/include/can_houbolt b/include/can_houbolt index cac3fb2f..ef63dc05 160000 --- a/include/can_houbolt +++ b/include/can_houbolt @@ -1 +1 @@ -Subproject commit cac3fb2f79a7c608d7d25834baaa7e29e79256a8 +Subproject commit ef63dc052591c2f54347955e9b21596fe64f6cfe diff --git a/src/SequenceManager.cpp b/src/SequenceManager.cpp index 3acefc01..59ccbcf6 100644 --- a/src/SequenceManager.cpp +++ b/src/SequenceManager.cpp @@ -281,8 +281,9 @@ void SequenceManager::StartSequence(nlohmann::json jsonSeq, nlohmann::json jsonA sequenceRunning = true; sequenceThread = std::thread(&SequenceManager::sequenceLoop, this, interval_us); //sequenceThread.detach(); + std::string sequence_name = jsonSeq["data"][0]["desc"]; - Debug::print("Sequence Started"); + Debug::print("Sequence Started " + sequence_name); } } diff --git a/src/can/Control.cpp b/src/can/Control.cpp index 0f07d880..ef626bf3 100644 --- a/src/can/Control.cpp +++ b/src/can/Control.cpp @@ -5,43 +5,42 @@ #include "can/Control.h" const std::vector Control::states = - { - "Enabled", - "Target", - "Threshold", - "Hysteresis", - "ActuatorChannelID", - "SensorChannelID", - "RefreshDivider", - "RequestStatus", - "ResetAllSettings" - }; + { + "Enabled", + "Target", + "Threshold", + "Hysteresis", + "ActuatorChannelID", + "SensorChannelID", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings" + }; const std::map> Control::scalingMap = - { - {"Enabled", {1.0, 0.0}}, - {"Position", {1.0, 0.0}}, - {"Target", {0.003735, 0.0}}, - {"Threshold", {1.0, 0.0}}, - {"Hysteresis", {0.003735, 0.0}}, - {"ActuatorChannelID", {1.0, 0.0}}, - {"SensorChannelID", {1.0, 0.0}}, - {"RefreshDivider", {1.0, 0.0}}, - }; - -const std::map Control::variableMap = - { - {CONTROL_ENABLED, "Enabled"}, - {CONTROL_TARGET, "Target"}, - {CONTROL_THRESHOLD, "Threshold"}, - {CONTROL_HYSTERESIS, "Hysteresis"}, - {CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, - {CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, - {CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, - }; + { + {"Enabled", {1.0, 0.0}}, + {"Target", {0.001189720812, -15.19667413}}, + {"Threshold", {1.0, 0.0}}, + {"Hysteresis", {0.001189720812, -15.19667413}}, + {"ActuatorChannelID", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, + }; + +const std::map Control::variableMap = + { + {CONTROL_ENABLED, "Enabled"}, + {CONTROL_TARGET, "Target"}, + {CONTROL_THRESHOLD, "Threshold"}, + {CONTROL_HYSTERESIS, "Hysteresis"}, + {CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, + {CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, + }; Control::Control(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) - : Channel("Control", channelID, std::move(channelName), sensorScaling, parent, CONTROL_DATA_N_BYTES), NonNodeChannel(parent) + : Channel("Control", channelID, std::move(channelName), sensorScaling, parent, CONTROL_DATA_N_BYTES), NonNodeChannel(parent) { commandMap = { {"SetEnabled", {std::bind(&Control::SetEnabled, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, @@ -87,25 +86,25 @@ void Control::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLengt { switch (canMsg->bit.cmd_id) { - case CONTROL_RES_GET_VARIABLE: - case CONTROL_RES_SET_VARIABLE: - GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); - break; - case CONTROL_RES_STATUS: - StatusResponse(canMsg, canMsgLength, timestamp); - break; - case CONTROL_RES_RESET_SETTINGS: - ResetSettingsResponse(canMsg, canMsgLength, timestamp); - break; - case CONTROL_REQ_RESET_SETTINGS: - case CONTROL_REQ_STATUS: - case CONTROL_REQ_SET_VARIABLE: - case CONTROL_REQ_GET_VARIABLE: - //TODO: comment out after testing - //throw std::runtime_error("request message type has been received, major fault in protocol"); - break; - default: - throw std::runtime_error("Control specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + case CONTROL_RES_GET_VARIABLE: + case CONTROL_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case CONTROL_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case CONTROL_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case CONTROL_REQ_RESET_SETTINGS: + case CONTROL_REQ_STATUS: + case CONTROL_REQ_SET_VARIABLE: + case CONTROL_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("Control specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); } } catch (std::exception &e) @@ -341,12 +340,12 @@ void Control::RequestResetSettings(std::vector ¶ms, bool testOnly) void Control::RequestCurrentState() { std::vector params; - - GetEnabled(params, false); - GetTarget(params, false); - GetThreshold(params, false); - GetHysteresis(params, false); - GetActuatorChannelID(params, false); - GetSensorChannelID(params, false); - GetRefreshDivider(params, false); + + GetEnabled(params, false); + GetTarget(params, false); + GetThreshold(params, false); + GetHysteresis(params, false); + GetActuatorChannelID(params, false); + GetSensorChannelID(params, false); + GetRefreshDivider(params, false); } \ No newline at end of file diff --git a/src/can/Node.cpp b/src/can/Node.cpp index 15bf9cc2..88990cce 100644 --- a/src/can/Node.cpp +++ b/src/can/Node.cpp @@ -14,6 +14,7 @@ #include "can/Servo.h" #include "can/PneumaticValve.h" #include "can/Control.h" +#include "can/PIControl.h" #include "can/IMU.h" #include "can/Rocket.h" #include "StateController.h" @@ -188,6 +189,9 @@ void Node::InitChannels(NodeInfoMsg_t &nodeInfo, std::map(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); break; + case CHANNEL_TYPE_PI_CONTROL: + ch = new PIControl(channelID, std::get<0>(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); + break; case CHANNEL_TYPE_IMU: ch = new IMU(channelID, std::get<0>(channelInfo[channelID]), std::get<1>(channelInfo[channelID]), this); break; diff --git a/src/can/PIControl.cpp b/src/can/PIControl.cpp new file mode 100644 index 00000000..ed8de803 --- /dev/null +++ b/src/can/PIControl.cpp @@ -0,0 +1,453 @@ +// +// Created by Markus on 03.09.21. +// + +#include "can/PIControl.h" + +const std::vector PIControl::states = + { + "Enabled", + "Target", + "P", + "I", + "SensorSlope", + "SensorOffset", + "OperatingPoint", + "ActuatorChannelID", + "SensorChannelID", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings" + }; + +const std::map> PIControl::scalingMap = + { + {"Enabled", {1.0, 0.0}}, + {"Target", {0.001, 0.0}}, + {"P", {0.001, 0.0}}, + {"I", {0.001, 0.0}}, + {"SensorSlope", {0.001, 0.0}}, + {"SensorOffset", {0.001, 0.0}}, + {"OperatingPoint", {0.001, 0.0}}, + {"ActuatorChannelID", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, + }; + +const std::map PIControl::variableMap = + { + {PI_CONTROL_ENABLED, "Enabled"}, + {PI_CONTROL_TARGET, "Target"}, + {PI_CONTROL_P, "P"}, + {PI_CONTROL_I, "I"}, + {PI_CONTROL_SENSOR_SLOPE, "SensorSlope"}, + {PI_CONTROL_SENSOR_OFFSET, "SensorOffset"}, + {PI_CONTROL_OPERATING_POINT, "OperatingPoint"}, + {PI_CONTROL_ACTUATOR_CHANNEL_ID, "ActuatorChannelID"}, + {PI_CONTROL_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {PI_CONTROL_REFRESH_DIVIDER, "RefreshDivider"}, + }; + +PIControl::PIControl(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) + : Channel("PIControl", channelID, std::move(channelName), sensorScaling, parent, PI_CONTROL_DATA_N_BYTES), NonNodeChannel(parent) +{ + commandMap = { + {"SetEnabled", {std::bind(&PIControl::SetEnabled, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetEnabled", {std::bind(&PIControl::GetEnabled, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetTarget", {std::bind(&PIControl::SetTarget, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetTarget", {std::bind(&PIControl::GetTarget, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetP", {std::bind(&PIControl::SetP, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetP", {std::bind(&PIControl::GetP, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetI", {std::bind(&PIControl::SetI, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetI", {std::bind(&PIControl::GetI, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorSlope", {std::bind(&PIControl::SetSensorSlope, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorSlope", {std::bind(&PIControl::GetSensorSlope, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorOffset", {std::bind(&PIControl::SetSensorOffset, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorOffset", {std::bind(&PIControl::GetSensorOffset, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetOperatingPoint", {std::bind(&PIControl::SetOperatingPoint, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetOperatingPoint", {std::bind(&PIControl::GetOperatingPoint, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetActuatorChannelID", {std::bind(&PIControl::SetActuatorChannelID, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetActuatorChannelID", {std::bind(&PIControl::GetActuatorChannelID, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetSensorChannelID", {std::bind(&PIControl::SetSensorChannelID, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetSensorChannelID", {std::bind(&PIControl::GetSensorChannelID, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"SetRefreshDivider", {std::bind(&PIControl::SetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, + {"GetRefreshDivider", {std::bind(&PIControl::GetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"RequestStatus", {std::bind(&PIControl::RequestStatus, this, std::placeholders::_1, std::placeholders::_2), {}}}, + {"RequestResetSettings", {std::bind(&PIControl::RequestResetSettings, this, std::placeholders::_1, std::placeholders::_2), {}}}, + }; +} + +//---------------------------------------------------------------------------------------// +//-------------------------------GETTER & SETTER Functions-------------------------------// +//---------------------------------------------------------------------------------------// + +std::vector PIControl::GetStates() +{ + std::vector states = PIControl::states; + for (auto &state : states) + { + state = GetStatePrefix() + state; + } + return states; +} + +//-------------------------------------------------------------------------------// +//-------------------------------RECEIVE Functions-------------------------------// +//-------------------------------------------------------------------------------// + +void PIControl::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, uint64_t ×tamp) +{ + try + { + switch (canMsg->bit.cmd_id) + { + case PI_CONTROL_RES_GET_VARIABLE: + case PI_CONTROL_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case PI_CONTROL_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case PI_CONTROL_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case PI_CONTROL_REQ_RESET_SETTINGS: + case PI_CONTROL_REQ_STATUS: + case PI_CONTROL_REQ_SET_VARIABLE: + case PI_CONTROL_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("PIControl specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + } + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl '" + this->channelName + "' - ProcessCANCommand: " + std::string(e.what())); + } +} + +//----------------------------------------------------------------------------// +//-------------------------------SEND Functions-------------------------------// +//----------------------------------------------------------------------------// + +void PIControl::SetEnabled(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("Enabled"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ENABLED, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetEnabled: " + std::string(e.what())); + } +} + +void PIControl::GetEnabled(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ENABLED, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetEnabled: " + std::string(e.what())); + } +} + +void PIControl::SetTarget(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("Target"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_TARGET, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetTarget: " + std::string(e.what())); + } +} + +void PIControl::GetTarget(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_TARGET, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetTarget: " + std::string(e.what())); + } +} + +void PIControl::SetP(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("P"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetP: " + std::string(e.what())); + } +} + +void PIControl::GetP(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetP: " + std::string(e.what())); + } +} + +void PIControl::SetI(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("I"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetI: " + std::string(e.what())); + } +} + +void PIControl::GetI(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetI: " + std::string(e.what())); + } +} + +void PIControl::SetSensorSlope(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorSlope"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_SLOPE, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorSlope: " + std::string(e.what())); + } +} + +void PIControl::GetSensorSlope(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_SLOPE, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorSlope: " + std::string(e.what())); + } +} + +void PIControl::SetSensorOffset(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorOffset"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_OFFSET, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorOffset: " + std::string(e.what())); + } +} + +void PIControl::GetSensorOffset(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_OFFSET, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorOffset: " + std::string(e.what())); + } +} + +void PIControl::SetOperatingPoint(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("OperatingPoint"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_OPERATING_POINT, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetOperatingPoint: " + std::string(e.what())); + } +} + +void PIControl::GetOperatingPoint(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_OPERATING_POINT, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetOperatingPoint: " + std::string(e.what())); + } +} + +void PIControl::SetActuatorChannelID(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("ActuatorChannelID"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ACTUATOR_CHANNEL_ID, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetActuatorChannelID: " + std::string(e.what())); + } +} + +void PIControl::GetActuatorChannelID(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_ACTUATOR_CHANNEL_ID, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetActuatorChannelID: " + std::string(e.what())); + } +} + +void PIControl::SetSensorChannelID(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("SensorChannelID"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_CHANNEL_ID, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetSensorChannelID: " + std::string(e.what())); + } +} + +void PIControl::GetSensorChannelID(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_SENSOR_CHANNEL_ID, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetSensorChannelID: " + std::string(e.what())); + } +} + +void PIControl::SetRefreshDivider(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("RefreshDivider"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_REFRESH_DIVIDER, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetRefreshDivider: " + std::string(e.what())); + } +} + +void PIControl::GetRefreshDivider(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_REFRESH_DIVIDER, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetRefreshDivider: " + std::string(e.what())); + } +} + +void PIControl::RequestStatus(std::vector ¶ms, bool testOnly) +{ + try + { + SendNoPayloadCommand(params, parent->GetNodeID(), PI_CONTROL_REQ_STATUS, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - RequestStatus: " + std::string(e.what())); + } +} + +void PIControl::RequestResetSettings(std::vector ¶ms, bool testOnly) +{ + try + { + SendNoPayloadCommand(params, parent->GetNodeID(), PI_CONTROL_REQ_RESET_SETTINGS, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - RequestResetSettings: " + std::string(e.what())); + } +} + +void PIControl::RequestCurrentState() +{ + std::vector params; + + GetEnabled(params, false); + GetTarget(params, false); + GetP(params, false); + GetI(params, false); + GetSensorSlope(params, false); + GetSensorOffset(params, false); + GetOperatingPoint(params, false); + GetActuatorChannelID(params, false); + GetSensorChannelID(params, false); + GetRefreshDivider(params, false); +} \ No newline at end of file diff --git a/src/can/Servo.cpp b/src/can/Servo.cpp index 97b0e962..c78173ca 100644 --- a/src/can/Servo.cpp +++ b/src/can/Servo.cpp @@ -5,68 +5,67 @@ #include "can/Servo.h" const std::vector Servo::states = - { - "Position", - "TargetPosition", - "TargetPressure", - "MaxSpeed", - "MaxAccel", - "MaxTorque", - "P", - "I", - "D", - "SensorChannelID", - "Startpoint", - "Endpoint", - "PWMEnabled", - "PositionRaw", - "RefreshDivider", - "RequestStatus", - "ResetAllSettings" - }; + { + "Position", + "TargetPosition", + "TargetPressure", + "MaxSpeed", + "MaxAccel", + "MaxTorque", + "P", + "I", + "D", + "SensorChannelID", + "Startpoint", + "Endpoint", + "PWMEnabled", + "PositionRaw", + "RefreshDivider", + "RequestStatus", + "ResetAllSettings"}; const std::map> Servo::scalingMap = - { - {"Position", {0.00152590219, 0.0}}, - {"TargetPosition", {0.00152590219, 0.0}}, - {"TargetPressure", {1.0, 0.0}}, - {"MaxSpeed", {1.0, 0.0}}, - {"MaxAccel", {1.0, 0.0}}, - {"MaxTorque", {1.0, 0.0}}, - {"P", {1.0, 0.0}}, - {"I", {1.0, 0.0}}, - {"D", {1.0, 0.0}}, - {"SensorChannelID", {1.0, 0.0}}, - {"Startpoint", {1.0, 0.0}}, - {"Endpoint", {1.0, 0.0}}, - {"PWMEnabled", {1.0, 0.0}}, - {"PositionRaw", {1.0, 0.0}}, - {"MovePosition", {1.0, 0.0}}, - {"MoveInterval", {1.0, 0.0}}, - {"RefreshDivider", {1.0, 0.0}}, - }; - -const std::map Servo::variableMap = - { - {SERVO_POSITION, "Position"}, - {SERVO_TARGET_POSITION, "TargetPosition"}, - {SERVO_TARGET_PRESSURE, "TargetPressure"}, - {SERVO_MAX_SPEED, "MaxSpeed"}, - {SERVO_MAX_ACCEL, "MaxAccel"}, - {SERVO_MAX_TORQUE, "MaxTorque"}, - {SERVO_P_PARAM, "P"}, - {SERVO_I_PARAM, "I"}, - {SERVO_D_PARAM, "D"}, - {SERVO_SENSOR_CHANNEL_ID, "SensorChannelID"}, - {SERVO_POSITION_STARTPOINT, "Startpoint"}, - {SERVO_POSITION_ENDPOINT, "Endpoint"}, - {SERVO_PWM_ENABLED, "PWMEnabled"}, - {SERVO_POSITION_RAW, "PositionRaw"}, - {SERVO_SENSOR_REFRESH_DIVIDER, "RefreshDivider"}, - }; + { + {"Position", {0.00152590219, 0.0}}, + {"TargetPosition", {0.00152590219, 0.0}}, + {"TargetPressure", {1.0, 0.0}}, + {"MaxSpeed", {1.0, 0.0}}, + {"MaxAccel", {1.0, 0.0}}, + {"MaxTorque", {1.0, 0.0}}, + {"P", {1.0, 0.0}}, + {"I", {1.0, 0.0}}, + {"D", {1.0, 0.0}}, + {"SensorChannelID", {1.0, 0.0}}, + {"Startpoint", {1.0, 0.0}}, + {"Endpoint", {1.0, 0.0}}, + {"PWMEnabled", {1.0, 0.0}}, + {"PositionRaw", {1.0, 0.0}}, + {"MovePosition", {1.0, 0.0}}, + {"MoveInterval", {1.0, 0.0}}, + {"RefreshDivider", {1.0, 0.0}}, +}; + +const std::map Servo::variableMap = + { + {SERVO_POSITION, "Position"}, + {SERVO_TARGET_POSITION, "TargetPosition"}, + {SERVO_TARGET_PRESSURE, "TargetPressure"}, + {SERVO_MAX_SPEED, "MaxSpeed"}, + {SERVO_MAX_ACCEL, "MaxAccel"}, + {SERVO_MAX_TORQUE, "MaxTorque"}, + {SERVO_P_PARAM, "P"}, + {SERVO_I_PARAM, "I"}, + {SERVO_D_PARAM, "D"}, + {SERVO_SENSOR_CHANNEL_ID, "SensorChannelID"}, + {SERVO_POSITION_STARTPOINT, "Startpoint"}, + {SERVO_POSITION_ENDPOINT, "Endpoint"}, + {SERVO_PWM_ENABLED, "PWMEnabled"}, + {SERVO_POSITION_RAW, "PositionRaw"}, + {SERVO_SENSOR_REFRESH_DIVIDER, "RefreshDivider"}, +}; Servo::Servo(uint8_t channelID, std::string channelName, std::vector sensorScaling, Node *parent) - : Channel("Servo", channelID, std::move(channelName), sensorScaling, parent, SERVO_DATA_N_BYTES), NonNodeChannel(parent) + : Channel("Servo", channelID, std::move(channelName), sensorScaling, parent, SERVO_DATA_N_BYTES), NonNodeChannel(parent) { commandMap = { {"SetPosition", {std::bind(&Servo::SetPosition, this, std::placeholders::_1, std::placeholders::_2), {"Value"}}}, @@ -101,7 +100,7 @@ Servo::Servo(uint8_t channelID, std::string channelName, std::vector sen {"GetRefreshDivider", {std::bind(&Servo::GetRefreshDivider, this, std::placeholders::_1, std::placeholders::_2), {}}}, {"RequestStatus", {std::bind(&Servo::RequestStatus, this, std::placeholders::_1, std::placeholders::_2), {}}}, {"RequestResetSettings", {std::bind(&Servo::RequestResetSettings, this, std::placeholders::_1, std::placeholders::_2), {}}}, - {"RequestMove", {std::bind(&Servo::RequestMove, this, std::placeholders::_1, std::placeholders::_2),{"Position","TimeInterval"}}}, + {"RequestMove", {std::bind(&Servo::RequestMove, this, std::placeholders::_1, std::placeholders::_2), {"Position", "TimeInterval"}}}, }; } @@ -129,25 +128,25 @@ void Servo::ProcessCANCommand(Can_MessageData_t *canMsg, uint32_t &canMsgLength, { switch (canMsg->bit.cmd_id) { - case SERVO_RES_GET_VARIABLE: - case SERVO_RES_SET_VARIABLE: - GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); - break; - case SERVO_RES_STATUS: - StatusResponse(canMsg, canMsgLength, timestamp); - break; - case SERVO_RES_RESET_SETTINGS: - ResetSettingsResponse(canMsg, canMsgLength, timestamp); - break; - case SERVO_REQ_RESET_SETTINGS: - case SERVO_REQ_STATUS: - case SERVO_REQ_SET_VARIABLE: - case SERVO_REQ_GET_VARIABLE: - //TODO: comment out after testing - //throw std::runtime_error("request message type has been received, major fault in protocol"); - break; - default: - throw std::runtime_error("Servo specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); + case SERVO_RES_GET_VARIABLE: + case SERVO_RES_SET_VARIABLE: + GetSetVariableResponse(canMsg, canMsgLength, timestamp, variableMap, scalingMap); + break; + case SERVO_RES_STATUS: + StatusResponse(canMsg, canMsgLength, timestamp); + break; + case SERVO_RES_RESET_SETTINGS: + ResetSettingsResponse(canMsg, canMsgLength, timestamp); + break; + case SERVO_REQ_RESET_SETTINGS: + case SERVO_REQ_STATUS: + case SERVO_REQ_SET_VARIABLE: + case SERVO_REQ_GET_VARIABLE: + // TODO: comment out after testing + // throw std::runtime_error("request message type has been received, major fault in protocol"); + break; + default: + throw std::runtime_error("Servo specific command with command id not supported: " + std::to_string(canMsg->bit.cmd_id)); } } catch (std::exception &e) @@ -272,7 +271,6 @@ void Servo::GetTargetPressure(std::vector ¶ms, bool testOnly) } } - void Servo::SetMaxSpeed(std::vector ¶ms, bool testOnly) { std::vector scalingParams = scalingMap.at("MaxSpeed"); @@ -585,7 +583,7 @@ void Servo::RequestMove(std::vector ¶ms, bool testOnly) { try { - if (params.size() != 2) //number of required parameters + if (params.size() != 2) // number of required parameters { throw std::runtime_error("2 parameters expected, but " + std::to_string(params.size()) + " were provided"); } @@ -593,10 +591,10 @@ void Servo::RequestMove(std::vector ¶ms, bool testOnly) std::vector scalingInterval = scalingMap.at("MoveInterval"); ServoMoveMsg_t moveMsg = {0}; - moveMsg.position = Channel::ScaleAndConvertInt32(params[0],scalingPosition[0],scalingPosition[1]); - moveMsg.interval = Channel::ScaleAndConvertInt32(params[1],scalingInterval[0],scalingInterval[1]); + moveMsg.position = Channel::ScaleAndConvertInt32(params[0], scalingPosition[0], scalingPosition[1]); + moveMsg.interval = Channel::ScaleAndConvertInt32(params[1], scalingInterval[0], scalingInterval[1]); - SendStandardCommand(parent->GetNodeID(), SERVO_REQ_MOVE, (uint8_t *) &moveMsg, sizeof(moveMsg), parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + SendStandardCommand(parent->GetNodeID(), SERVO_REQ_MOVE, (uint8_t *)&moveMsg, sizeof(moveMsg), parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { @@ -632,19 +630,19 @@ void Servo::RequestCurrentState() { std::vector params; - GetPosition(params, false); + GetPosition(params, false); GetPositionRaw(params, false); - GetTargetPosition(params, false); - GetTargetPressure(params, false); - GetMaxSpeed(params, false); - GetMaxAccel(params, false); - GetMaxTorque(params, false); - GetP(params, false); - GetI(params, false); - GetD(params, false); - GetSensorChannelID(params, false); - GetStartpoint(params, false); - GetEndpoint(params, false); - GetPWMEnabled(params, false); - GetRefreshDivider(params, false); + GetTargetPosition(params, false); + GetTargetPressure(params, false); + GetMaxSpeed(params, false); + GetMaxAccel(params, false); + GetMaxTorque(params, false); + GetP(params, false); + GetI(params, false); + GetD(params, false); + GetSensorChannelID(params, false); + GetStartpoint(params, false); + GetEndpoint(params, false); + GetPWMEnabled(params, false); + GetRefreshDivider(params, false); } \ No newline at end of file From 2b184ae00fb06bb60f29b507c448026def7e1f6c Mon Sep 17 00:00:00 2001 From: spaceteamofficial Date: Sat, 4 Jan 2025 14:58:05 +0100 Subject: [PATCH 19/19] - updated PI Controller to allow asymetric responses - Added debug output for every can message - Removed the RequestCurrentState in the CANManager init(why??) Raffael Rott (Just committing no idea who made these changes --- include/can/PIControl.h | 14 +++-- include/can_houbolt | 2 +- src/can/CANManager.cpp | 14 ++++- src/can/PIControl.cpp | 120 +++++++++++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/include/can/PIControl.h b/include/can/PIControl.h index 9a11f68e..b1e250a0 100644 --- a/include/can/PIControl.h +++ b/include/can/PIControl.h @@ -36,11 +36,17 @@ class PIControl : public Channel, public NonNodeChannel void SetTarget(std::vector ¶ms, bool testOnly); void GetTarget(std::vector ¶ms, bool testOnly); - void SetP(std::vector ¶ms, bool testOnly); - void GetP(std::vector ¶ms, bool testOnly); + void SetP_POS(std::vector ¶ms, bool testOnly); + void GetP_POS(std::vector ¶ms, bool testOnly); - void SetI(std::vector ¶ms, bool testOnly); - void GetI(std::vector ¶ms, bool testOnly); + void SetI_POS(std::vector ¶ms, bool testOnly); + void GetI_POS(std::vector ¶ms, bool testOnly); + + void SetP_NEG(std::vector ¶ms, bool testOnly); + void GetP_NEG(std::vector ¶ms, bool testOnly); + + void SetI_NEG(std::vector ¶ms, bool testOnly); + void GetI_NEG(std::vector ¶ms, bool testOnly); void SetSensorSlope(std::vector ¶ms, bool testOnly); void GetSensorSlope(std::vector ¶ms, bool testOnly); diff --git a/include/can_houbolt b/include/can_houbolt index ef63dc05..1e5a2ef2 160000 --- a/include/can_houbolt +++ b/include/can_houbolt @@ -1 +1 @@ -Subproject commit ef63dc052591c2f54347955e9b21596fe64f6cfe +Subproject commit 1e5a2ef28e63694ed6ec2c6c5626a4745c90b262 diff --git a/src/can/CANManager.cpp b/src/can/CANManager.cpp index c3314659..02bad3f6 100644 --- a/src/can/CANManager.cpp +++ b/src/can/CANManager.cpp @@ -195,7 +195,7 @@ CANResult CANManager::Init(Config &config) initialized = true; Debug::print("Request current state and config from nodes...\n"); - RequestCurrentState(); + //RequestCurrentState(); } catch (std::exception& e) @@ -387,7 +387,17 @@ void CANManager::OnCANRecv(uint8_t canBusChannelID, uint32_t canID, uint8_t *pay { if (canIDStruct->info.direction == 0) { - Debug::print("Direction bit master to node from node %d on bus %d, delegating msg...", nodeID, canBusChannelID); + Debug::print("Direction bit master to node %d on bus %d, delegating msg...", nodeID, canBusChannelID); + std::string msg; + msg += "\nNode ID: " + std::to_string(nodeID) + "\n"; + msg += "Channel ID: " + std::to_string(canMsg->bit.info.channel_id) + "\n"; + msg += "CMD ID: " + std::to_string(canMsg->bit.cmd_id) + "\n"; + for (int i = 0; i < payloadLength; i++) + { + msg += std::to_string(canMsg->bit.data.uint8[i]) + " "; + } + msg += "\n"; + Debug::print(msg); //TODO: DIRTY HOTFIX, remove it std::vector channels = {0,1,2,3}; channels.erase(channels.begin()+canBusChannelID); diff --git a/src/can/PIControl.cpp b/src/can/PIControl.cpp index ed8de803..760eca70 100644 --- a/src/can/PIControl.cpp +++ b/src/can/PIControl.cpp @@ -8,8 +8,10 @@ const std::vector PIControl::states = { "Enabled", "Target", - "P", - "I", + "P_POS", + "I_POS", + "P_NEG", + "I_NEG", "SensorSlope", "SensorOffset", "OperatingPoint", @@ -24,8 +26,10 @@ const std::map> PIControl::scalingMap = { {"Enabled", {1.0, 0.0}}, {"Target", {0.001, 0.0}}, - {"P", {0.001, 0.0}}, - {"I", {0.001, 0.0}}, + {"P_POS", {0.001, 0.0}}, + {"I_POS", {0.001, 0.0}}, + {"P_NEG", {0.001, 0.0}}, + {"I_NEG", {0.001, 0.0}}, {"SensorSlope", {0.001, 0.0}}, {"SensorOffset", {0.001, 0.0}}, {"OperatingPoint", {0.001, 0.0}}, @@ -38,8 +42,10 @@ const std::map PIControl::variableMap = { {PI_CONTROL_ENABLED, "Enabled"}, {PI_CONTROL_TARGET, "Target"}, - {PI_CONTROL_P, "P"}, - {PI_CONTROL_I, "I"}, + {PI_CONTROL_P_POS, "P_POS"}, + {PI_CONTROL_I_POS, "I_POS"}, + {PI_CONTROL_P_NEG, "P_NEG"}, + {PI_CONTROL_I_NEG, "I_NEG"}, {PI_CONTROL_SENSOR_SLOPE, "SensorSlope"}, {PI_CONTROL_SENSOR_OFFSET, "SensorOffset"}, {PI_CONTROL_OPERATING_POINT, "OperatingPoint"}, @@ -56,10 +62,14 @@ PIControl::PIControl(uint8_t channelID, std::string channelName, std::vector ¶ms, bool testOnly) } } -void PIControl::SetP(std::vector ¶ms, bool testOnly) +void PIControl::SetP_POS(std::vector ¶ms, bool testOnly) { - std::vector scalingParams = scalingMap.at("P"); + std::vector scalingParams = scalingMap.at("P_POS"); try { - SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_POS, scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - SetP: " + std::string(e.what())); + throw std::runtime_error("PIControl - SetP_POS: " + std::string(e.what())); } } -void PIControl::GetP(std::vector ¶ms, bool testOnly) +void PIControl::GetP_POS(std::vector ¶ms, bool testOnly) { try { - GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P, + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_POS, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - GetP: " + std::string(e.what())); + throw std::runtime_error("PIControl - GetP_POS: " + std::string(e.what())); } } -void PIControl::SetI(std::vector ¶ms, bool testOnly) +void PIControl::SetI_POS(std::vector ¶ms, bool testOnly) { - std::vector scalingParams = scalingMap.at("I"); + std::vector scalingParams = scalingMap.at("I_POS"); try { - SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_POS, scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - SetI: " + std::string(e.what())); + throw std::runtime_error("PIControl - SetI_POS: " + std::string(e.what())); } } -void PIControl::GetI(std::vector ¶ms, bool testOnly) +void PIControl::GetI_POS(std::vector ¶ms, bool testOnly) { try { - GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I, + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_POS, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); } catch (std::exception &e) { - throw std::runtime_error("PIControl - GetI: " + std::string(e.what())); + throw std::runtime_error("PIControl - GetI_POS: " + std::string(e.what())); + } +} + +void PIControl::SetP_NEG(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("P_NEG"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_NEG, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetP_NEG: " + std::string(e.what())); + } +} + +void PIControl::GetP_NEG(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_P_NEG, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetP_NEG: " + std::string(e.what())); + } +} + +void PIControl::SetI_NEG(std::vector ¶ms, bool testOnly) +{ + std::vector scalingParams = scalingMap.at("I_NEG"); + + try + { + SetVariable(PI_CONTROL_REQ_SET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_NEG, + scalingParams, params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - SetI_NEG: " + std::string(e.what())); + } +} + +void PIControl::GetI_NEG(std::vector ¶ms, bool testOnly) +{ + try + { + GetVariable(PI_CONTROL_REQ_GET_VARIABLE, parent->GetNodeID(), PI_CONTROL_I_NEG, + params, parent->GetCANBusChannelID(), parent->GetCANDriver(), testOnly); + } + catch (std::exception &e) + { + throw std::runtime_error("PIControl - GetI_NEG: " + std::string(e.what())); } } @@ -442,8 +508,10 @@ void PIControl::RequestCurrentState() GetEnabled(params, false); GetTarget(params, false); - GetP(params, false); - GetI(params, false); + GetP_POS(params, false); + GetI_POS(params, false); + GetP_NEG(params, false); + GetI_NEG(params, false); GetSensorSlope(params, false); GetSensorOffset(params, false); GetOperatingPoint(params, false);